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 Rectangle
creates an implementation block&self
is shorthand forself: &Self
whereSelf
aliases 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.width
accesses 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
self
parameter for instance behavior - Associated functions: Type-scoped functions for construction/utilities
impl
blocks: Organize type-related functionality- Automatic referencing: Eliminates manual pointer dereferencing
Next: Enums provide another approach to custom types with variant-based data modeling.