Skip to content

Intermediate · Structs, enums and pattern matching · topic 19 of 50 · 25 min

Methods and associated functions

Explanation

Methods are functions attached to a type, written inside an impl block. Their first parameter is self: &self to read, &mut self to change, or self to take ownership. Functions in impl without self are associated functions, called with Type::name(), and a function named new is the usual way to build an instance.

Why methods?

A struct holds data. Methods add behaviour that belongs with that data. Instead of a loose function area(rect: &Rect), you write rect.area(). The code is easier to find, and editors can list everything a type can do when you type a dot.

rust
#[derive(Debug)]
struct Rect {
    width: u32,
    height: u32,
}

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

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

fn main() {
    let room = Rect { width: 12, height: 10 };
    let bed = Rect { width: 6, height: 7 };
    println!("{} {}", room.area(), room.can_hold(&bed));   // 120 true
}
impl Rect { ... } holds everything Rect can do. self is the instance the method is called on.
Think of it like this: Think of a TV and its remote. The TV is the struct (the data: channel, volume). The buttons on the remote are its methods. Some buttons only show information (&self), some change the TV (&mut self), and one, like "give away the TV", hands the whole thing over (self).

A method is still an ordinary function underneath. room.area() is exactly the same as Rect::area(&room). The dot form is simply easier to read and lets you chain calls, like text.trim().to_uppercase().

The three kinds of self

First parameterMeaningUse when
&selfBorrow the instance to readMost methods: area, is_empty, total
&mut selfBorrow the instance to change itdeposit, push, rename
selfTake ownership of the instanceTurning it into something else; it cannot be used afterwards
rust
struct Wallet {
    balance: u64,
}

impl Wallet {
    fn balance(&self) -> u64 {
        self.balance
    }
    fn add(&mut self, amount: u64) {
        self.balance += amount;
    }
    fn close(self) -> u64 {
        println!("closing");
        self.balance          // the Wallet is consumed
    }
}
A method and a field can share a name; w.balance() calls the method, w.balance reads the field.

You do not write &w or &mut w when calling. Rust does automatic referencing: w.add(100) is turned into Wallet::add(&mut w, 100) for you. But w itself must still be declared mut to call a &mut self method.

Associated functions and new

A function inside impl without a self parameter is an associated function. It belongs to the type, not to one instance, and you call it with two colons: Type::function(). You have already used one: String::from. By convention, a constructor function called new creates an instance. Self (capital S) is a short name for the type the impl is for.

rust
impl Wallet {
    fn new() -> Self {
        Self { balance: 0 }
    }
    fn with_balance(amount: u64) -> Self {
        Self { balance: amount }
    }
}

fn main() {
    let mut w = Wallet::new();
    w.add(500);
    let w2 = Wallet::with_balance(1000);
    println!("{} {}", w.balance(), w2.balance());   // 500 1000
    let last = w.close();
    // w.balance();   // error: w was moved by close(self)
    println!("{last}");
}
Rust has no special constructor syntax; new is just a normal associated function.

Several impl blocks

A type may have many impl blocks, even in different files of the same crate. This is useful for grouping related methods. You will also write impl blocks to implement traits (module 7).

Common mistakes

  • Forgetting self: fn area() -> u32 inside impl is an associated function, so rect.area() fails. Add &self.
  • Calling a &mut self method on a non-mut variable: "cannot borrow as mutable". Use let mut.
  • Using self by value by accident and then losing the instance. Use &self unless you mean to consume it.
  • Calling an associated function with a dot (w.new()). Use Wallet::new().
Tip: If a method returns a big field, return a reference (&self.name) rather than cloning it; let the caller clone if they need ownership.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Methods and associated functions · 1/6

impl Rect { fn area(&self) ... }

Methods are functions that belong to a type. You write them inside an impl block.

Prefer a video by a teacher? Find free videos on “rust methods impl self associated functions new” on YouTube ↗

In real life: A metro card

A city metro app models each travel card as a struct with methods: recharge(&mut self) adds money, tap_in(&mut self, fare) deducts a fare if the balance allows it, and balance(&self) shows the amount. new() creates a card with a minimum balance. All the rules for a card live in one impl block, so the ticket gates and the recharge kiosk use exactly the same logic.

rust
struct MetroCard { balance: u32 }

impl MetroCard {
    fn new() -> Self { Self { balance: 100 } }
    fn recharge(&mut self, amount: u32) { self.balance += amount; }
    fn tap_in(&mut self, fare: u32) -> bool {
        if self.balance >= fare {
            self.balance -= fare;
            true
        } else {
            false
        }
    }
}

fn main() {
    let mut card = MetroCard::new();
    card.recharge(50);
    println!("{} {}", card.tap_in(60), card.balance);   // true 90
}
tap_in returns true when the fare could be paid.

Practice (0/3 done)

Task 1 · Question

What does this print? struct Counter { n: u32 } impl Counter { fn inc(&mut self) { self.n += 1; } } fn main() { let mut c = Counter { n: 0 }; c.inc(); c.inc(); c.inc(); println!("{}", c.n); }

Task 2 · Question

How do you call an associated function named new on a struct Order?

Task 3 · Practice

Build a struct BankAccount { holder: String, balance: i64 } with: new(holder: &str) -> Self, deposit(&mut self, amt: i64), withdraw(&mut self, amt: i64) -> bool (refuse if not enough money), and summary(&self) -> String. Use them in main.

Quick quiz

Question 1

1.Which first parameter lets a method change the instance?

Question 2

2.What is Self inside impl Wallet?

Question 3

3.After calling a method that takes self (not &self), what happens to the instance?

Question 4

4.Is new a special keyword in Rust?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which first parameter lets a method change the instance?

Answer: &mut self

&mut self is a mutable borrow of the instance.

What is Self inside impl Wallet?

Answer: A short name for the type Wallet

Self (capital S) refers to the type; self (small s) to the instance.

After calling a method that takes self (not &self), what happens to the instance?

Answer: It is moved into the method and cannot be used afterwards

Taking self by value consumes the instance.

Is new a special keyword in Rust?

Answer: No, it is just a conventional name for an associated function

Rust has no constructor keyword; new is a convention.

What does -> impl Iterator<Item = u32> promise?Related

Answer: The function returns some single concrete type that is an iterator of u32

impl Trait in return position hides one concrete type.

From Generics and trait bounds

What happens if marks = [] is written in the class body and each student appends to self.marks?Related

Answer: All students share one list

A class attribute is shared. Mutable per-object data must be created in __init__.

From Classes and objects

Can you implement std's Display for std's Vec<u8> in your crate?Related

Answer: No, because of the orphan rule

Neither the trait nor the type is yours. Wrap Vec in a newtype instead.

From Traits: shared behaviour

Which function signature is valid Rust?Related

Answer: fn add(a: i32, b: i32) -> i32

Each parameter is name: type, and the return type follows ->.

From Functions, statements and expressions

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Structs, enums and pattern matching” before its exam: Enums and Option, Pattern matching: match, if let and let else.
  2. Module 5 exam: Structs, enums and pattern matching · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Error handling and collections. 50 topics left in the course before the final exam.

← Previous: Structs: your own data types