8.12 Methods and Associated Functions
Rust allows associating functions directly with structs, enums, and traits using impl (implementation) blocks. These associated functions come in two main forms: methods and associated functions (often called “static methods” in other languages).
- Methods: Functions that operate on an instance of a type. Their first parameter is always written as
self,&self, or&mut self. These represent the instance itself, an immutable borrow of the instance, or a mutable borrow, respectively. Methods are called using dot notation (instance.method()).- Note on
SelfType: These parameter forms (self,&self,&mut self) are actually shorthand forself: Self,self: &Self, andself: &mut Self. Here,Self(capital ‘S’) is a special type alias within animplblock that refers to the type the block is implementing (e.g.,Circlewithinimpl Circle { ... }). This shows thatselfparameters still follow the standardparameter: Typesyntax.
- Note on
- Associated Functions: Functions associated with a type but not tied to a specific instance. They do not take
selfas the first parameter. They are called using the type name and::syntax (Type::function()). They are commonly used for constructors or utility functions related to the type.
8.12.1 Defining and Calling Methods and Associated Functions
struct Circle {
radius: f64,
}
// Implementation block for the Circle struct (Here, Self = Circle)
impl Circle {
// Associated function: often used as a constructor.
// Does not take 'self'. Called using Circle::new(...).
pub fn new(radius: f64) -> Self { // 'Self' refers to the type 'Circle'
if radius < 0.0 {
panic!("Radius cannot be negative");
}
Circle { radius }
}
// Method: takes an immutable reference ('self: &Self').
// Called using my_circle.area().
pub fn area(&self) -> f64 { // Short for 'self: &Self' or 'self: &Circle'
std::f64::consts::PI * self.radius * self.radius
}
// Method: takes a mutable reference ('self: &mut Self').
// Called using my_circle.scale(...).
pub fn scale(&mut self, factor: f64) { //Short for 'self: &mut Self' (&mut Circle)
if factor < 0.0 {
panic!("Scale factor cannot be negative");
}
self.radius *= factor;
}
// Method: takes ownership ('self: Self').
// Called using my_circle.consume(). The instance cannot be used afterwards.
pub fn consume(self) { // Short for 'self: Self' or 'self: Circle'
println!("Consuming circle with radius {}", self.radius);
// 'self' (the circle instance) is dropped here.
}
}
fn main() {
// Call associated function (constructor)
let mut my_circle = Circle::new(5.0);
// Call methods using dot notation
println!("Initial Area: {}", my_circle.area());
my_circle.scale(2.0); // Calls the mutable method
println!("Scaled Radius: {}", my_circle.radius);
println!("Scaled Area: {}", my_circle.area());
// Call method that consumes the instance
// my_circle.consume();
// println!("Area after consume: {}", my_circle.area());
// Error: use of moved value 'my_circle'
// Alternative way to call methods (less common):
// Explicitly pass the instance reference.
let radius = 10.0;
let another_circle = Circle::new(radius);
let area = Circle::area(&another_circle); // Equivalent to another_circle.area()
println!("Area of another circle: {}", area);
}
As noted in Section 6.3.1, Rust performs automatic referencing and dereferencing for method calls. When using the dot operator (object.method()), the compiler automatically inserts the appropriate &, &mut, or * to match the method’s self, &self, or &mut self receiver as required.