Step 2 of 6
Default methods
A trait can give a method a default implementation: a body that every implementing type gets for free unless it writes its own. A default method can call the trait's other methods, even the required ones that don't have a body yet: whatever type it ends up running on is guaranteed to have them.
trait LogEntry {
fn level(&self) -> String;
fn message(&self) -> String;
fn render(&self) -> String {
format!("[{}] {}", self.level(), self.message())
}
}
struct Login {
user: String,
}
struct DiskFull {
percent: u32,
}
impl LogEntry for Login {
fn level(&self) -> String {
String::from("INFO")
}
fn message(&self) -> String {
format!("{} logged in", self.user)
}
}
impl LogEntry for DiskFull {
fn level(&self) -> String {
String::from("WARN")
}
fn message(&self) -> String {
format!("disk {}% full", self.percent)
}
fn render(&self) -> String {
format!("!!! [{}] {} !!!", self.level(), self.message())
}
}
fn main() {
let a = Login { user: String::from("ana") };
let b = DiskFull { percent: 97 };
println!("{}", a.render());
println!("{}", b.render());
}
[INFO] ana logged in
!!! [WARN] disk 97% full !!!
levelandmessageare required: no body, every type must write them.renderis a provided method: it has a body.Logindoesn't mentionrender, so it gets the default.DiskFullwrites its ownrender, which overrides the default for that type only.
This is how good traits are designed: a small core of required methods, plus convenience methods built on top. The standard library does it everywhere. The Iterator trait (coming up in the iterators module) has exactly one required method, next, and around 75 provided ones (map, filter, sum, count...) that all work by calling next.
Common mistakes
- Reading fields in a default method. A trait knows nothing about the fields of the types that will implement it. A default method can only use the trait's methods:
trait LogEntry {
fn render(&self) -> String {
format!("[INFO] {}", self.message)
}
}
fn main() {}
error[E0609]: no field `message` on type `&Self`
Self (capital S) means "whatever type implements this trait". The fix is to add a required method (fn message(&self) -> String;) and call it.
- Expecting to call the default from an override. Once a type overrides
render, there's no way to call the default version from inside it. If both need the same logic, put it in another method (asDiskFulldoes by callinglevel()andmessage()).
Your task
A support desk has two kinds of tickets. Give the Ticket trait a default method label that returns #<id> <title>, for example #42 Printer on fire. Bug should use the default. Incident should override label to put URGENT in front: URGENT #7 Database down.
Challenge 2
Give the Greeter trait a default method greet that returns Hello, <name>!. Visitor keeps the default; Pirate overrides it to return Ahoy, <name>!.
Challenge 3
Bank accounts. Write the trait Account yourself, with:
- a required method
fn balance(&self) -> i64(in cents), - a default method
fn overdraft_limit(&self) -> i64that returns0, - a default method
fn can_withdraw(&self, amount: i64) -> boolthat is true whenbalance + overdraft_limitis at leastamount.
Implement it for Savings (keeps both defaults) and Checking (overrides overdraft_limit to return 10000, so it may go 100 dollars below zero). Notice that can_withdraw calls overdraft_limit, so the override changes its result too.
Previous: Defining and implementing a trait Next: Traits as parameters: bounds and impl Trait