How to Define and Use Structs in Rust

Define a struct using the `struct` keyword followed by a name and a list of named fields, then instantiate it with struct literal syntax or a constructor method.

Define a struct using the struct keyword followed by a name and a list of named fields, then instantiate it with struct literal syntax or a constructor method. You access fields using dot notation, and you can add behavior by implementing traits or methods on the struct type.

Here is a basic example of defining a User struct, creating an instance, and accessing its data:

struct User {
    username: String,
    active: bool,
    login_count: u32,
}

fn main() {
    // Instantiate using struct literal syntax
    let user1 = User {
        username: String::from("alice"),
        active: true,
        login_count: 0,
    };

    // Access fields using dot notation
    println!("User: {}", user1.username);

    // Create a second instance using the "update syntax" to reuse fields
    let user2 = User {
        username: String::from("bob"),
        ..user1 // Copies 'active' and 'login_count' from user1
    };

    // Mutable access requires a mutable binding
    let mut user3 = user1;
    user3.active = false;
    user3.login_count += 1;
}

For more complex initialization logic, it is common to implement a constructor method. This pattern ensures invariants are checked before the struct is returned:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Constructor method
    fn new(w: u32, h: u32) -> Self {
        if w == 0 || h == 0 {
            panic!("Dimensions must be greater than zero");
        }
        Self { width: w, height: h }
    }

    // Method to calculate area
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(10, 20);
    println!("Area: {}", rect.area());
}

Key takeaways:

  1. Ownership: Structs containing owned types (like String) take ownership of those values. To share data without copying, use references (&String) or smart pointers (Rc, Arc).
  2. Immutability: Struct instances are immutable by default. Use let mut to modify fields.
  3. Tuple Structs: If field names aren't needed, use struct Point(f32, f32) for lightweight data.
  4. Unit Structs: Use struct Marker; for types that hold no data but serve as markers for traits.