10.5Methods and impl blocks

Last updated June 13, 2026

You've called methods since chapter 1. text.len(), name.trim(), guess.cmp(&secret), numbers.push(3): all of those are methods, functions that belong to a type and are called with a dot. This lesson shows you how to write your own, and you'll find that the only genuinely new thing is the syntax. The hard part, who owns the value and who's borrowing it, you finished in chapter 9.

A method is a function with a home

Start with a plain function that takes a Rectangle and returns its area:

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

fn area(rect: &Rectangle) -> f64 {
    rect.width * rect.height
}

fn main() {
    let r = Rectangle { width: 4.0, height: 2.5 };
    println!("{}", area(&r));
}
10

This works, but area is a loose function that happens to be about rectangles. Nothing ties it to Rectangle, and in a big program it floats off among unrelated functions. A method is the same function, attached to the type, so it lives with the data it operates on. You write methods inside an impl block (short for implementation):

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

fn main() {
    let r = Rectangle { width: 4.0, height: 2.5 };
    println!("{}", r.area());
}
10

Three things changed. The function moved inside impl Rectangle { ... }, which says "the following functions belong to Rectangle." The parameter rect: &Rectangle became &self, a special first parameter naming the instance the method is called on. And the call site went from area(&r) to r.area(): the receiver moves in front of the dot. r.area() is almost literally Rectangle::area(&r), and you can write it that way, but nobody does.

The impl block sits separate from the struct definition. The struct says what data a Rectangle holds; the impl says what a Rectangle can do. You can even have several impl blocks for one type; the compiler merges them.

The three forms of self

That &self parameter is the whole game, and it's the borrowing chapter you already passed. &self is shorthand for self: &Self, where Self is "the type this impl is for." A method's first parameter can take the receiver in exactly the three ways lesson 9.4 taught for any value:

&self borrows the instance immutably. The method can read fields but not change them, and the caller keeps full ownership. This is the most common form by far, the method equivalent of taking &Rectangle:

impl Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height       // reads, doesn't change
    }
}

&mut self borrows mutably. The method can change fields, and the caller must hold the instance in a mut binding. This is &mut Rectangle:

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

fn main() {
    let mut r = Rectangle { width: 4.0, height: 2.5 };
    r.scale(2.0);
    println!("{} x {}", r.width, r.height);
}
8 x 5

self (no &) takes ownership of the instance, consuming it. The caller can't use it afterward; this is the method form of passing a value by value, from lesson 8.6. It's rarer, used when a method transforms a value into something else and the original shouldn't live on:

impl Rectangle {
    fn into_square(self) -> Rectangle {
        let side = self.width.min(self.height);
        Rectangle { width: side, height: side }
    }
}

After let sq = r.into_square();, r is gone, moved into the method. Methods that consume self conventionally start with into_, a naming hint you'll see all over the standard library.

Key insight

Choosing between &self, &mut self, and self is the lesson 9.4 parameter decision, asked once more: does the method only read (&self), does it modify in place (&mut self), or does it consume and transform (self)? You already know how to answer. The method just writes the receiver before the dot instead of inside the parentheses.

Why methods read so well

The payoff is at the call site. Compare:

scale(&mut r, 2.0);     // free function: which argument is the rectangle?
r.scale(2.0);           // method: r is obviously the thing being scaled

The method form puts the subject first and the verb second, the way English does. r.scale(2.0).area() reads left to right as a chain of actions on r, and that chaining is why method syntax dominates real Rust. Methods also group discoverably: type r. in an editor with rust-analyzer (lesson 0.7) and it lists everything a Rectangle can do. Loose functions don't offer that.

Best practice

Default to &self. Reach for &mut self only when the method genuinely changes the instance, and for self only when it consumes it. A method that takes more than it needs (say &mut self for something that only reads) forces callers into a mut binding for no reason, the same over-borrowing lesson 9.4 warned about.

Methods take other parameters too

self is just the first parameter. After it, methods take arguments like any function:

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

fn main() {
    let big = Rectangle { width: 8.0, height: 6.0 };
    let small = Rectangle { width: 4.0, height: 2.0 };
    println!("{}", big.can_hold(&small));
}
true

can_hold borrows self immutably (it only compares) and borrows other immutably too, so neither rectangle is disturbed. The signature alone tells the caller that big.can_hold(&small) reads both and changes neither, which is exactly the contract lesson 9.1 praised.

Quiz time

Question #1

Add a method perimeter(&self) -> f64 to Rectangle and call it on a 3-by-5 rectangle.

Show solution
impl Rectangle {
    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }
}

fn main() {
    let r = Rectangle { width: 3.0, height: 5.0 };
    println!("{}", r.perimeter());
}

Prints 16. It only reads the fields, so &self is correct.

Question #2

For each method, say which receiver (&self, &mut self, or self) it should take:

a) returns whether the rectangle is a square b) doubles both dimensions c) consumes the rectangle and returns its area as an f64, discarding the shape

Show solution

a) &self: only reads. b) &mut self: changes fields in place; caller needs a mut binding. c) self: it consumes the rectangle (the shape is thrown away), so taking ownership is appropriate; conventionally you might name it into_area.

Question #3

This is refused. Why, and what's the fix?

impl Rectangle {
    fn grow(&self, extra: f64) {
        self.width += extra;
    }
}
Show solution

grow modifies self.width but takes &self, an immutable borrow, so the assignment is rejected (E0594, the same "cannot assign, behind a & reference" family as lesson 9.1's E0596). Change the receiver to &mut self. The method's job (changing the instance) decides the receiver.

Methods need an instance to call them on. The next lesson covers functions that belong to the type but don't take self, including ::new, which is how Rust builds instances without a special constructor language feature.