Using Structs to Structure Data
Named-field types, methods, associated functions — Rust's data-modeling primitives.
Part of Rust Interview Mastery · Based on The Rust Programming Language
Defining & Instantiating
Define and instantiate a struct — what are the rules?
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
let user = User {
email: String::from("a@b.com"),
username: String::from("vicky"),
active: true,
sign_in_count: 1,
};
Field order in the literal doesn't matter — the declaration does. Mutability is all or nothing: let mut user lets you change any field; you can't mark one field mutable. Access with user.email.
What are the shorthand and update syntaxes, and what does the update syntax move?
Field init shorthand — when a variable name matches the field: User { email, username, active: true, sign_in_count: 1 } instead of email: email.
Struct update syntax spreads the rest: let u2 = User { email: String::from("x"), ..u1 }; — remaining fields come from u1.
The catch interviewers want: fields moved out of u1 (like username: String) leave u1 partially moved and unusable for those fields. Copy fields (bool, u64) are copied so u1 stays partly usable. Update syntax is assignment — moves apply.
Tuple structs and unit structs — what are they for?
Tuple structs give a tuple a distinct name: struct Color(i32, i32, i32); — Color and Point are different types even with identical fields, which prevents mixing them up. Access by index: c.0.
Unit structs have no fields: struct AlwaysEqual; — used as marker types or when you need a type to implement a trait on but store nothing.
Both are type-system tools: nominal typing for semantics, not just data layout.
Why do the book's structs use String instead of &str for fields?
Because String is owned — the struct instance owns its data, so it can live as long as needed. &str is a borrow: a struct holding &str must carry a lifetime annotation and can't outlive the data it points at.
You can write struct User<'a> { name: &'a str } — chapter 10 covers it — but the owned version is the default choice. Rule of thumb: store owned data unless you have a measured reason to borrow.
Methods & Associated Functions
Walk through turning a function into a method — the Rectangle example.
struct Rectangle { width: u32, height: u32 }
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
The method lives in an impl block, and its first parameter is self: &self borrows immutably (read-only, the common case), &mut self to mutate, self consumes the instance (rare — transforms like into_inner). Calling rect.area() auto-references — no (*rect).area() needed.
What are associated functions and how do they differ from methods?
Associated functions are impl functions without self — called on the type with ::: String::from("hi"), Rectangle::square(10). They're the constructor namespace:
impl Rectangle {
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
}
Self is sugar for the impl'd type. Convention: new for the plain constructor, descriptive names (square, with_capacity) for variants. One type can have multiple impl blocks — relevant when generics add blocks per bound.
How do you print or debug a struct? Explain the derive system.
#[derive(Debug)]
struct Rectangle { width: u32, height: u32 }
println!("{:?}", r); // compact Debug
println!("{:#?}", r); // pretty Debug
dbg!(&r); // expr -> (file:line, value) to stderr, returns value
{} (Display) isn't derivable meaningfully — what should a Rectangle "look like" to a user? So Debug is the opt-in developer format. dbg! is the debugging workhorse: wraps an expression, logs file/line/value, and hands the value through — drop it into a chain without restructuring code.
When do you choose a struct vs a tuple vs an enum for modeling data?
- Struct: fields have names and distinct meanings; the type will grow; you want methods. Default choice for domain data.
- Tuple/tuple struct: 2–3 anonymous values where names add noise — a point, a return pair, a newtype wrapper.
- Enum: the value is one of several shapes — Option, Result, Message::Quit|Move|Write. Struct says "has all of these"; enum says "is one of these."
Mixing them up is the most common modeling smell in Rust codebases.