Method Syntax
Methods are functions defined within a struct’s context using impl blocks. The first parameter is always self, representing the instance being called.
Defining Methods
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!( "The area of the rectangle is {} square pixels.", rect1.area() ); }
Key Points:
impl Rectanglecreates an implementation block&selfis shorthand forself: &SelfwhereSelfaliases the impl type- Methods use dot notation:
rect1.area() - Borrowing rules apply:
&self(immutable),&mut self(mutable),self(take ownership)
Using &self preserves ownership in the caller, similar to function parameters. Methods consuming self are rare and typically used for transformations.
Methods vs Fields
Methods can have the same name as fields:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn width(&self) -> bool { self.width > 0 } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; if rect1.width() { println!("The rectangle has a nonzero width; it is {}", rect1.width); } }
rect1.width()calls the methodrect1.widthaccesses the field
This pattern enables getter methods for controlled field access, supporting public methods with private fields.
Automatic Referencing
Rust automatically handles referencing/dereferencing for method calls:
#[derive(Debug,Copy,Clone)] struct Point { x: f64, y: f64, } impl Point { fn distance(&self, other: &Point) -> f64 { let x_squared = f64::powi(other.x - self.x, 2); let y_squared = f64::powi(other.y - self.y, 2); f64::sqrt(x_squared + y_squared) } } let p1 = Point { x: 0.0, y: 0.0 }; let p2 = Point { x: 5.0, y: 6.5 }; p1.distance(&p2); (&p1).distance(&p2); // Equivalent
This eliminates the need for manual * or -> operators found in C/C++.
Methods with Parameters
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
Expected output:
Can rect1 hold rect2? true
Can rect1 hold rect3? false
Implementation:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; let rect2 = Rectangle { width: 10, height: 40, }; let rect3 = Rectangle { width: 60, height: 45, }; println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); }
Parameters after self work like regular function parameters. Use borrowing for parameters you don’t need to own.
Associated Functions
Functions in impl blocks without self are associated functions (similar to static methods):
Filename: src/main.rs
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size, } } } fn main() { let sq = Rectangle::square(3); }
Called using :: syntax: Rectangle::square(3). Common for constructors like String::from().
Multiple impl Blocks
Structs can have multiple impl blocks:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; let rect2 = Rectangle { width: 10, height: 40, }; let rect3 = Rectangle { width: 60, height: 45, }; println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); }
Multiple blocks are useful with generics and traits (Chapter 10).
Summary
Structs provide custom types with named fields and associated behavior through methods. Key concepts:
- Field access: Dot notation with borrowing considerations
- Methods: Functions with
selfparameter for instance behavior - Associated functions: Type-scoped functions for construction/utilities
implblocks: Organize type-related functionality- Automatic referencing: Eliminates manual pointer dereferencing
Next: Enums provide another approach to custom types with variant-based data modeling.