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:
- 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). - Immutability: Struct instances are immutable by default. Use
let mutto modify fields. - Tuple Structs: If field names aren't needed, use
struct Point(f32, f32)for lightweight data. - Unit Structs: Use
struct Marker;for types that hold no data but serve as markers for traits.