Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Self Type: These parameter forms (self, &self, &mut self) are actually shorthand for self: Self, self: &Self, and self: &mut Self. Here, Self (capital ‘S’) is a special type alias within an impl block that refers to the type the block is implementing (e.g., Circle within impl Circle { ... }). This shows that self parameters still follow the standard parameter: Type syntax.
  • Associated Functions: Functions associated with a type but not tied to a specific instance. They do not take self as 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.