C.W.K.
Stream
Lesson 02 of 07 · published

Default Method Implementations

~10 min · traits, default-methods

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

A trait method doesn't have to be abstract. You can give it a default implementation right in the trait, and types get it for free unless they override it.

Behavior for free

Provide a body in the trait definition, and every implementor inherits it without writing anything. A type can still override the default when it needs custom behavior. This is how a trait offers a rich API while requiring implementors to write only a small core.

Defaults calling required methods

The powerful move: a default method can call other methods of the same trait — including ones with no default. So you require implementors to provide one small method, and the trait builds a whole family of behaviors on top of it. The standard library's Iterator works exactly this way: implement next, get dozens of methods (map, filter, sum...) for free.

Require a little, provide a lot. The best trait design asks implementors for the minimal core method(s), then layers default methods on top. Iterator requires only next() and hands you 70+ adapter methods built on it. Design your traits the same way.

Why this beats inheritance

In class-based languages you'd get shared behavior by inheriting from a base class — dragging along its data and its place in a hierarchy. Default trait methods give you the shared behavior with none of the coupling: any type, anywhere, can opt into the trait and get the defaults, without being forced into a family tree.

Code

A default method, inherited and overridden·rust
trait Greet {
    fn name(&self) -> String;            // required — implementors supply this
    fn hello(&self) -> String {          // default — built on name()
        format!("Hello, {}!", self.name())
    }
}

struct Dog;
impl Greet for Dog {
    fn name(&self) -> String { "Rex".into() }
    // hello() comes for free
}

struct Robot;
impl Greet for Robot {
    fn name(&self) -> String { "Unit-7".into() }
    fn hello(&self) -> String { format!("BEEP. I am {}.", self.name()) } // override
}

fn main() {
    println!("{}", Dog.hello());   // Hello, Rex!
    println!("{}", Robot.hello()); // BEEP. I am Unit-7.
}

External links

Exercise

Add a default method loud_describe(&self) to your Describe trait that calls describe() and uppercases it. Implement the trait for one type without overriding the default, and one type that overrides it. Confirm both behave as expected.
Hint
The default calls the required describe(), so any implementor gets loud_describe free. Overriding it is opt-in — the same require-little-provide-lot pattern std uses for Iterator.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.