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."