Skip to content

Rust Structs

A struct groups related values into one named type. Where a tuple is a quick, unnamed bundle, a struct gives every field a name — which makes the code that uses it far easier to read.

struct Person {
name: String,
age: u32,
}
fn main() {
let alice = Person {
name: String::from("Alice"),
age: 30,
};
println!("{} is {}", alice.name, alice.age);
}

Output:

Alice is 30
▶ Try it Yourself

Compare this to a tuple ("Alice", 30) — with a struct, alice.name and alice.age are self-explanatory. You don’t have to remember that field 0 is the name and field 1 is the age.

Like variables, struct instances are immutable by default. Add mut to change a field after creating it:

struct Person {
name: String,
age: u32,
}
fn main() {
let mut alice = Person {
name: String::from("Alice"),
age: 30,
};
alice.age += 1;
println!("{} is now {}", alice.name, alice.age);
}
▶ Try it Yourself

You can attach functions to a struct using an impl block. Inside, &self refers to the specific instance the method was called on:

struct Person {
name: String,
age: u32,
}
impl Person {
fn greet(&self) {
println!("Hi, I'm {} and I'm {}", self.name, self.age);
}
}
fn main() {
let alice = Person { name: String::from("Alice"), age: 30 };
alice.greet();
}

Output:

Hi, I'm Alice and I'm 30
▶ Try it Yourself

A common pattern is a new function inside impl that builds an instance for you, so callers don’t have to write out every field by hand:

struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: &str, age: u32) -> Person {
Person { name: name.to_string(), age }
}
}
fn main() {
let bob = Person::new("Bob", 25);
println!("{} is {}", bob.name, bob.age);
}
▶ Try it Yourself

Notice age on its own inside Person { name: name.to_string(), age } — when a variable has the exact same name as the field, you can skip writing age: age and just write age. Rust calls this field init shorthand.

Try println!("{:?}", alice) on a struct as written above, and it won’t compile — Rust doesn’t know how to debug-print a custom type unless you tell it to. Add #[derive(Debug)] above the struct to get that for free:

#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
let alice = Person { name: String::from("Alice"), age: 30 };
println!("{:?}", alice);
}

Output:

Person { name: "Alice", age: 30 }
▶ Try it Yourself

#[derive(...)] is Rust’s way of auto-generating common functionality for a type — you’ll see Debug, and later Clone and others, added this way constantly.

  • A struct groups named fields into one type: struct Person { name: String, age: u32 }.
  • Create an instance with Person { name: ..., age: ... }; mark it mut to change fields later.
  • Add behavior with an impl block; &self refers to the instance a method was called on.
  • Add #[derive(Debug)] to make a struct printable with {:?}.

Quick check

1. What's the main advantage of a struct over a tuple?

2. What does adding #[derive(Debug)] to a struct let you do?

3. In Person { name: name.to_string(), age }, what does the bare age do?

Score: 0 / 3