Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

traits with Multiple Methods and Default Implementations

Goal of This Episode

Learn to define multiple methods in a trait, and use default implementations so implementers only override what they need.

Concept

When Chapter 4 introduced traits, ours had just one method each. In fact a trait can have many methods, and some can come with a default implementation — a pre-written “generic version” that implementers can override if they don’t like it.

Multiple Methods

trait Describe {
    fn name(&self) -> String;
    fn description(&self) -> String;
}

fn main() {}

When implementing, every method must be provided:

trait Describe {
    fn name(&self) -> String;
    fn description(&self) -> String;
}

struct Cat;

impl Describe for Cat {
    fn name(&self) -> String { ... }
    fn description(&self) -> String { ... }
}

fn main() {}

Default Implementations

Some methods can ship with a sensible default version:

trait Describe {
    fn name(&self) -> String;

    fn description(&self) -> String {
        let n = self.name();
        let mut result = String::from("I am ");
        result.push_str(&n);
        result
    }
}

fn main() {}

description has a default implementation that calls .name() to build the string. When implementing Describe, you only need to supply .name()description() automatically uses the default.

Of course, you can also override the default with your own version.

Example Code

trait Describe {
    // A method that must be implemented
    fn name(&self) -> String;

    // A default implementation: usable as-is, or overridable
    fn description(&self) -> String {
        let n = self.name();
        let mut result = String::from("I am ");
        result.push_str(&n);
        result
    }
}

struct Cat {
    nickname: String,
}

struct Dog {
    nickname: String,
}

// Cat implements only name; description uses the default
impl Describe for Cat {
    fn name(&self) -> String {
        self.nickname.clone()
    }
}

// Dog overrides description
impl Describe for Dog {
    fn name(&self) -> String {
        self.nickname.clone()
    }

    fn description(&self) -> String {
        let n = self.name();
        let mut result = String::from("Woof! My name is ");
        result.push_str(&n);
        result.push_str(", and I'm a dog!");
        result
    }
}

fn main() {
    let cat = Cat { nickname: String::from("Tangerine") };
    let dog = Dog { nickname: String::from("Shiba") };

    // Cat uses the default description
    println!("{}", cat.name());
    println!("{}", cat.description());

    // Dog uses its custom description
    println!("{}", dog.name());
    println!("{}", dog.description());
}

Recap

  • A trait can define multiple methods.
  • Methods can have a default implementation — write { ... } after the method instead of ;.
  • Default implementations can call other methods of the same trait.
  • When implementing a trait, methods with defaults may be skipped (using the default) or overridden.