Intermediate · Structs, enums and pattern matching · topic 19 of 50 · 25 min
Methods and associated functions
Explanation
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.
#[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
}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 parameter | Meaning | Use when |
|---|---|---|
| &self | Borrow the instance to read | Most methods: area, is_empty, total |
| &mut self | Borrow the instance to change it | deposit, push, rename |
| self | Take ownership of the instance | Turning it into something else; it cannot be used afterwards |
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
}
}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.
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}");
}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().
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
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.
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
}Practice (0/3 done)
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); }
How do you call an associated function named new on a struct Order?
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Enums and OptionStructs, enums and pattern matching · same module
- Pattern matching: match, if let and let elseStructs, enums and pattern matching · same module
- Generics and trait boundsModules, crates, traits and generics
- Classes and objectsPython Programming · Object-oriented programming
- Traits: shared behaviourModules, crates, traits and generics
- Ownership and functionsOwnership: Rust's big idea
Questions people ask about this
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.
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.
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 ->.
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
- 2 more topics in “Structs, enums and pattern matching” before its exam: Enums and Option, Pattern matching: match, if let and let else.
- Module 5 exam: Structs, enums and pattern matching · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Error handling and collections. 50 topics left in the course before the final exam.