Web Analytics

Methods & Associated Functions

Intermediate ~25 min read

Methods are functions that are defined within the context of a struct (or enum or trait object). Their first parameter is always self, which represents the instance of the struct the method is being called on. Methods let you organize functionality that belongs to a type.

Defining Methods

Methods are defined within an impl (implementation) block:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
The self Parameter: The &self is short for self: &Self. Within an impl block, Self is an alias for the type that the impl block is for.
Output
Click Run to execute your code

Methods vs Associated Functions

Methods vs Associated Functions

The self Parameter

Methods can take ownership of self, borrow it immutably, or borrow it mutably:

1. &self (Immutable Borrow)

Most common. Read-only access to the instance:

fn area(&self) -> u32 {
    self.width * self.height
}

2. &mut self (Mutable Borrow)

When you need to modify the instance:

fn scale(&mut self, factor: u32) {
    self.width *= factor;
    self.height *= factor;
}

3. self (Take Ownership)

Rare. Consumes the instance:

fn into_string(self) -> String {
    format!("{}x{}", self.width, self.height)
}

Associated Functions

Functions within impl blocks that don't take self are called associated functions. They're often used as constructors:

Output
Click Run to execute your code
Naming Convention: By convention, associated functions that create new instances are named new, but this isn't enforced by the language.

Multiple impl Blocks

You can have multiple impl blocks for the same type:

Output
Click Run to execute your code

Automatic Referencing and Dereferencing

Rust automatically adds &, &mut, or * to match the method signature:

let rect = Rectangle { width: 30, height: 50 };

// These are equivalent
rect.area();
(&rect).area();
Automatic Behavior: When you call a method with object.method(), Rust automatically adds &, &mut, or * so object matches the signature of the method.

Method Call Syntax

Type Syntax Example
Method instance.method() rect.area()
Associated Function Type::function() Rectangle::new(30, 50)

Best Practices

  • Use &self by default: Only use &mut self or self when needed
  • Create constructors: Use new() as an associated function
  • Group related functionality: Put all methods for a type in impl blocks
  • Use descriptive names: Method names should clearly indicate what they do
  • Return Self for chaining: Return &mut Self to enable method chaining
Caution: When you use self (taking ownership), the instance is consumed and can no longer be used. Only use this when you're transforming the instance into something else, like converting it to a different type.

Common Mistakes

1. Forgetting to use &self, &mut self, or self

// Wrong - Missing self parameter
impl Rectangle {
    fn area() -> u32 {  // Error: no self parameter
        self.width * self.height
    }
}

// Correct
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

2. Using &mut self when &self would suffice

// Wrong - Unnecessary mutability
impl Rectangle {
    fn area(&mut self) -> u32 {  // Doesn't need to mutate
        self.width * self.height
    }
}

// Correct - Use &self for read-only operations
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

3. Confusing methods with associated functions

// Wrong - Trying to call associated function like a method
let rect = Rectangle::new(30, 50);
let area = rect.new(30, 50);  // Error: new doesn't have self

// Correct - Use :: for associated functions
let rect = Rectangle::new(30, 50);
let area = rect.area();  // Use . for methods

Exercise: Methods Practice

Task: Implement methods for BankAccount and Temperature structs.

Output
Click Run to execute your code
Show Solution
impl BankAccount {
    fn new(owner: String, initial_balance: f64) -> BankAccount {
        BankAccount {
            balance: initial_balance,
            owner,
        }
    }
    
    fn deposit(&mut self, amount: f64) {
        self.balance += amount;
    }
    
    fn withdraw(&mut self, amount: f64) -> bool {
        if self.balance >= amount {
            self.balance -= amount;
            true
        } else {
            false
        }
    }
    
    fn balance(&self) -> f64 {
        self.balance
    }
}

impl Temperature {
    fn from_celsius(celsius: f64) -> Temperature {
        Temperature { celsius }
    }
    
    fn from_fahrenheit(fahrenheit: f64) -> Temperature {
        Temperature {
            celsius: (fahrenheit - 32.0) * 5.0 / 9.0,
        }
    }
    
    fn to_fahrenheit(&self) -> f64 {
        self.celsius * 9.0 / 5.0 + 32.0
    }
    
    fn is_freezing(&self) -> bool {
        self.celsius <= 0.0
    }
}

Summary

  • Methods are functions defined in impl blocks
  • Methods have self as first parameter
  • &self: Immutable borrow (most common)
  • &mut self: Mutable borrow (for modifications)
  • self: Takes ownership (rare)
  • Associated functions don't have self
  • Call methods with instance.method()
  • Call associated functions with Type::function()
  • Rust automatically handles referencing/dereferencing
  • Multiple impl blocks are allowed

What's Next?

Now that you understand structs and methods, you'll learn about enums - another way to create custom types. Enums let you define a type by enumerating its possible variants, and they're incredibly powerful when combined with pattern matching.