impl, self and associated functions

In Rust methods live in an impl block, outside the struct declaration. The first parameter, self, decides what the method may do: &self reads, &mut self writes, and self takes ownership. A function without a self parameter is an associated function and is called with Type::name.

This is the replacement for a C++ class body: the data stays in the struct, the behaviour goes in one or more impl blocks, and the access rules come from the type of self rather than from a keyword. The lesson keeps the methods free of other concepts so that each receiver type is visible.

A method on &self

fn area(&self) -> u32 borrows the value for the length of the call and cannot change it. The body reads the fields through self.width, and the caller writes r.area() without passing anything.

The program builds a rectangle and prints twelve. The method takes a shared reference, so the rectangle is still owned by main and could be used again after the call.

struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let r = Rectangle { width: 3, height: 4 }; println!("{}", r.area()); }

rustc --edition 2024 methods.rs
./methods

12

The dot is sugar for a function call with the receiver as the first argument, which is why &self matches a caller that holds the value rather than a reference to it. The lesson on shared references explains the borrow.

A method on &mut self

A method that changes a field takes &mut self. The caller needs a mutable binding, and while the call runs the value is exclusively borrowed, so nothing else may read it. The body can then assign to the fields directly.

The counter starts at zero, the method is called twice, and the field is two. Each call borrows the counter mutably for its own duration, and the two borrows do not overlap.

struct Counter { value: u32, } impl Counter { fn bump(&mut self) { self.value += 1; } } fn main() { let mut counter = Counter { value: 0 }; counter.bump(); counter.bump(); println!("{}", counter.value); }

rustc --edition 2024 mutself.rs
./mutself

2

The rule from mutable borrows applies unchanged: one writer at a time. A method that needs to both read and write takes &mut self and gets both through the same reference.

Associated functions

A function in an impl block without a self parameter belongs to the type, not to a value. It is called as Rectangle::new(2, 5), and by convention new returns a value of the type. The body can use Self instead of repeating the name.

The program below builds a rectangle through the associated function and prints both fields. There is no hidden allocation and no constructor keyword: the function returns the struct literal.

struct Rectangle { width: u32, height: u32, } impl Rectangle { fn new(width: u32, height: u32) -> Self { Self { width, height } } } fn main() { let r = Rectangle::new(2, 5); println!("{} {}", r.width, r.height); }

rustc --edition 2024 assoc.rs
./assoc

2 5

String::from and Vec::new are the same shape in the standard library, which is why the course has been calling associated functions since the first lesson.

A method that consumes

A method whose receiver is self takes ownership. The value is moved into the method and dropped at its end unless it is returned, which is how a builder turns one type into another or how a value is unpacked into its parts.

The program calls sum on a pair and prints five. The pair is gone after the call, so a second call would be E0382, exactly as in ownership.

struct Pair { a: i32, b: i32, } impl Pair { fn sum(self) -> i32 { self.a + self.b } } fn main() { let pair = Pair { a: 2, b: 3 }; println!("{}", pair.sum()); }

rustc --edition 2024 consuming.rs
./consuming

5

A consuming method is the right choice when the value has no use afterwards, and a &self one when it does. The lesson on Drop shows what happens to the fields when the method ends.

A method that needs mut

Calling a &mut self method on an immutable binding is E0596 at the call site. The error points at the receiver expression and the help adds mut to the binding, which is the smallest fix.

The program below declares the counter without mut and calls bump. The compiler rejects the call before looking at the method body.

struct Counter { value: u32, } impl Counter { fn bump(&mut self) { self.value += 1; } } fn main() { let counter = Counter { value: 0 }; counter.bump(); println!("{}", counter.value); }

rustc --edition 2024 not_mut_err.rs

error[E0596]: cannot borrow `counter` as mutable, as it is not declared as mutable
--> not_mut_err.rs:13:5
|
13 | counter.bump();
| ^^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
12 | let mut counter = Counter { value: 0 };
| +++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0596`.

The same code appears as E0594 when the assignment is written inside the method with a &self receiver, which the lesson on const and mutability shows. The two errors are the two ends of the same rule.

Exercises

  1. Add a method that returns the longer side of the rectangle. Which receiver does it need?
  2. Write an associated function that returns a square and call it twice with different sizes.
  3. Change the consuming method to take &self and call it twice on the same pair. What changes in the program, and what does the compiler allow now?

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does the first parameter of a method decide?

    Answer

    What the method may do with the value

    &self reads, &mut self writes, and self takes ownership; the receiver is the access rule.

  2. How is an associated function called?

    Answer

    On the type, with Type::name

    Without a self parameter the function belongs to the type, so the type is the path.

  3. What happens to a value whose method takes self?

    Answer

    It is moved into the method

    A self receiver consumes the value, so the caller may not use it again.

  4. Why does a method on &mut self need a mutable binding at the call site?

    Answer

    Because the method may change the value

    The call borrows the value mutably, and only a mutable binding can be borrowed that way.

  5. What does this program print? struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let r = Rectangle { width: 3, height: 4 }; println!("{}", r.area()); }

    Answer

    12

    The method multiplies the two fields, and three times four is twelve.

  6. What does this program print? struct Counter { value: u32, } impl Counter { fn bump(&mut self) { self.value += 1; } } fn main() { let mut counter = Counter { value: 0 }; counter.bump(); counter.bump(); println!("{}", counter.value); }

    Answer

    2

    Each call writes once through a mutable borrow, and the field ends at two.

  7. What does this program print? struct Rectangle { width: u32, height: u32, } impl Rectangle { fn new(width: u32, height: u32) -> Self { Self { width, height } } } fn main() { let r = Rectangle::new(2, 5); println!("{} {}", r.width, r.height); }

    Answer

    2 5

    The associated function builds the literal from its two arguments.

  8. What does this program print? struct Pair { a: i32, b: i32, } impl Pair { fn sum(self) -> i32 { self.a + self.b } } fn main() { let pair = Pair { a: 2, b: 3 }; println!("{}", pair.sum()); }

    Answer

    5

    The consuming method adds the two fields before dropping the pair, and the sum is five.

  9. What is the first line of the compiler output for this program? struct Counter { value: u32, } impl Counter { fn bump(&mut self) { self.value += 1; } } fn main() { let counter = Counter { value: 0 }; counter.bump(); println!("{}", counter.value); }

    Answer

    error[E0596]: cannot borrow counter as mutable, as it is not declared as mutable

    The call needs a mutable borrow of the binding, and the help adds mut to the let.

  10. What does this program print? struct Counter { value: u32, } impl Counter { fn bump(&mut self) { self.value += 1; } fn get(&self) -> u32 { self.value } } fn main() { let mut counter = Counter { value: 5 }; counter.bump(); println!("{}", counter.get()); }

    Answer

    6

    The mutable call ends before get starts, and get reads the incremented value.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev