Now that you understand ownership and borrowing, let's build something real. Structs let you group related data together. Methods let you attach behavior to that data.
What is a Struct?
A struct is a custom data type that groups multiple values together. Think of it like a record or a blueprint.
struct User {
username: String,
email: String,
active: bool,
}
This defines a User with three fields. To create one:
let user = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
active: true,
};
Accessing Fields
Use dot notation to read or write fields:
println!("{}", user.username); // alice
user.active = false; // modify the field
Methods with impl
Methods live inside an impl block. The first parameter is always self (or &self for read-only methods):
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// &self means we borrow the rectangle, don't take ownership
fn area(&self) -> u32 {
self.width * self.height
}
// Can also modify self with &mut self
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// Associated function - doesn't take self
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
Usage:
let rect = Rectangle { width: 30, height: 50 };
println!("Area: {}", rect.area()); // 1500
let sq = Rectangle::square(10);
println!("Square area: {}", sq.area()); // 100
Why &self Instead of self?
Remember ownership? If you wrote fn area(self) instead of fn area(&self), calling it would consume the rectangle:
fn area(self) -> u32 { // takes ownership
self.width * self.height
}
let rect = Rectangle { width: 30, height: 50 };
let a = rect.area();
// rect is now invalid! Can't use it here.
By using &self, we borrow the struct. The caller keeps ownership.
The -> Operator (Cradle Robbers Need Not Apply)
Unlike C/C++, Rust doesn't have a separate arrow operator for pointers. Dot notation auto-dereferences:
let r = ▭
r.area(); // Works fine, no r->area() needed
Tuple Structs
Sometimes you just want to name your tuple:
struct Color(u8, u8, u8);
struct Point(f64, f64);
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);
Useful when you need a simple wrapper without named fields.
Unit-Like Structs
You can define structs with no fields — useful for markers or traits:
struct AlwaysEqual;
What's Next
You've now got the tools to model real things. Next up: Enums & Pattern Matching — handling the messiness of real data, like "this could be one of several things."