Skip to content
AgileLogics
6 min read

Methods & impl Blocks

Prerequisites: Structs

Methods are functions attached to a type. You define them in an impl (implementation) block for that type.

Syntax

impl Rectangle {
    fn new(width: f64, height: f64) -> Self { // associated function
        Self { width, height }
    }

    fn area(&self) -> f64 {                   // reads the instance
        self.width * self.height
    }

    fn scale(&mut self, factor: f64) {        // modifies the instance
        self.width *= factor;
        self.height *= factor;
    }
}
  • The first parameter decides how the method uses the instance:
    • &self — borrows it immutably (most common: reading).
    • &mut self — borrows it mutably (changing fields). The instance must be declared mut.
    • self — takes ownership; the instance can't be used afterwards. Useful for methods that transform a value into something else.
  • Self is a shorthand for the type the impl block is for.
  • Functions in an impl block without a self parameter are associated functions. They're called with ::, like Rectangle::new(3.0, 4.0) or String::from("hi"). By convention a constructor is called new.
  • Methods are called with a dot: rect.area(). Rust automatically adds the & or &mut for you, so you don't write (&rect).area().

Example

#[derive(Debug)]
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    fn new(width: f64, height: f64) -> Self {
        Self { width, height }
    }

    fn square(size: f64) -> Self {
        Self::new(size, size)
    }

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

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }

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

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

fn main() {
    let mut rect = Rectangle::new(3.0, 4.0);
    let small = Rectangle::square(2.0);

    println!("area: {}", rect.area());
    println!("can hold small? {}", rect.can_hold(&small));

    rect.scale(2.0);
    println!("after scaling: {rect:?}");
    println!("new area: {}", rect.area());

    let text = rect.describe(); // `rect` is moved into `describe`
    println!("{text}");
}

Expected output

area: 12
can hold small? true
after scaling: Rectangle { width: 6.0, height: 8.0 }
new area: 48
6 x 8 rectangle

Notice that Debug prints floats as 6.0, while Display ({}) prints 6.

Common mistakes

  • Calling a &mut self method on an immutable variable:
let counter = Counter { count: 0 };
counter.increment(); // increment takes &mut self
error[E0596]: cannot borrow `counter` as mutable, as it is not declared as mutable

Declare it with let mut counter.

  • Calling an associated function with a dot, like rect.new(1.0, 2.0). Functions without self are called on the type: Rectangle::new(1.0, 2.0).
  • Forgetting self. inside a method. Fields are only reachable through self — width on its own is an unknown variable.
  • Using self when you meant &self. A method taking self consumes the value, so the caller can't use it afterwards.

Practice

Create a Counter struct with a count: u32 field and an impl block with:

  • new() — returns a counter starting at 0,
  • increment(&mut self) — adds 1,
  • value(&self) -> u32 — returns the current count.

Increment a counter three times and print Count: 3.

Show solution
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Self {
        Self { count: 0 }
    }

    fn increment(&mut self) {
        self.count += 1;
    }

    fn value(&self) -> u32 {
        self.count
    }
}

fn main() {
    let mut counter = Counter::new();
    counter.increment();
    counter.increment();
    counter.increment();
    println!("Count: {}", counter.value());
}