Step 3 of 7
Associated functions and Self: constructors
A function inside an impl block that does not take self is an associated function: it belongs to the type, but isn't called on a value. (Strictly, every function in an impl block is an associated function and methods are the ones that take self, but in everyday speech "associated function" means one without self.) You call it with the type's name and ::. You've used them since the first modules: String::from("hi"), String::new(), Vec::new().
Their most common job is building values. By convention, the main one is called new:
struct Item {
name: String,
price_cents: u32,
quantity: u32,
}
impl Item {
fn new(name: &str, price_cents: u32) -> Self {
Self {
name: name.to_string(),
price_cents,
quantity: 0,
}
}
fn free_sample(name: &str) -> Self {
Self::new(name, 0)
}
fn summary(&self) -> String {
format!("{} x{} at {} cents", self.name, self.quantity, self.price_cents)
}
}
fn main() {
let mut tea = Item::new("Tea", 450);
tea.quantity = 12;
let sticker = Item::free_sample("Sticker");
println!("{}", tea.summary());
println!("{}", sticker.summary());
}
Tea x12 at 450 cents
Sticker x0 at 0 cents
fn new(...) -> Selfreturns a newItem. Inside an impl,Selfis the type (Item), soSelf { ... }builds one. WritingItemworks too, butSelfkeeps working if you rename the type.Item::new("Tea", 450)calls it with::, because there's no value yet to put before a dot.- A type can have several constructors with descriptive names.
free_samplecallsSelf::newso the defaults live in one place.
Why constructors?
new is not special syntax: Rust has no built-in constructors, just this convention. It's still worth it. Callers don't need to know every field (quantity starts at 0 without them thinking about it), the defaults live in one place, and later, with private fields (the modules module), a constructor becomes the only way to create a value, so it can enforce rules like "a name is never empty". Clippy even checks the convention: a new that doesn't return Self gets a new_ret_no_self warning.
:: or .?
| You write | Meaning |
|---|---|
Item::new("Tea", 450) |
associated function, called on the type |
tea.summary() |
method, called on a value |
Item::summary(&tea) |
the same method, called the long way |
Common mistakes
- A dot after the type name:
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let r = Rect.new(2, 3);
println!("{}", r.area());
}
error[E0423]: cannot find value `Rect` in this scope
- Calling a method like an associated function:
Rect::area()has no rectangle to measure:error[E0061]: this function takes 1 argument but 0 arguments were supplied. Call it on a value:r.area().
Your task
Write Player::new(name: &str) -> Self. A new player has the given name, level 1, 100 hp and a score of 0. Build the value with Self { ... }.
Challenge 2
Give Rect two constructors and a method:
fn new(width: u32, height: u32) -> Selffn square(size: u32) -> Self: a rectangle with equal sides. Build it by callingSelf::new.fn area(&self) -> u32
Challenge 3
Write an impl block for Timer, which stores a duration in seconds, with:
fn from_minutes(minutes: u32) -> Selffn from_hms(hours: u32, minutes: u32, seconds: u32) -> Selffn whole_minutes(&self) -> u32: the number of complete minutes (125seconds is2).
Previous: &mut self: methods that change the value Next: self: methods that take ownership