RustArena

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 !!!

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

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.

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:

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