Structs are how you model your domain, and impl blocks are where most Rust code lives.
A struct groups named fields into a new type. Instantiate it by naming every field; the field init shorthand lets you write User { name, active: true } when a variable has the same name as the field. Struct update syntax User { active: false, ..other } fills the rest from another value (moving any non-Copy fields out of it).
Rust also has tuple structs struct Meters(f64);, useful as lightweight wrappers (the newtype pattern) that stop you from mixing up a Meters and a Seconds, and unit structs struct Marker; with no fields.
Behaviour goes in an impl block. The first parameter decides how a method uses the value:
&selfreads it (most methods),&mut selfmodifies it,selfconsumes it, typically to transform it into something else.
Functions in an impl block without a self parameter are associated functions, called with Type::name(). By convention a constructor is fn new(...) -> Self. Self is an alias for the type being implemented.
Fields are private outside the module that defines the struct unless marked pub. Keeping fields private and exposing methods lets you maintain invariants, such as "width is never negative".
#[derive(Debug)] above the struct lets you print it with {:?} or, pretty-printed, {:#?}. You will meet more derivable traits later.
Method calls auto-borrow: rect.area() turns into Rectangle::area(&rect) automatically, so you rarely write & at a call site.
Example
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self { // associated function
Self { width, height } // field init shorthand
}
fn square(size: u32) -> Self {
Self::new(size, size)
}
fn area(&self) -> u32 { self.width * self.height }
fn scale(&mut self, k: u32) { self.width *= k; self.height *= k; }
fn into_tuple(self) -> (u32, u32) { (self.width, self.height) }
}
fn main() {
let mut r = Rectangle::new(3, 4);
r.scale(2);
println!("{} {:?}", r.area(), Rectangle::square(2)); // 48 Rectangle { width: 2, height: 2 }
let (w, h) = r.into_tuple(); // r is consumed
println!("{w}x{h}"); // 6x8
}Remember
- Methods take
&self,&mut selforselfdepending on how they use the value - Associated functions like
newhave noselfand are called withType::new - Tuple structs make cheap newtypes;
#[derive(Debug)]enables{:?}
Watch out for
- Taking
selfby value in a method that only reads, which consumes the caller's struct.