Skip to content

← Generics and Traits step 8 of 24

Medium Primitives

Supertraits and default methods

A trait can require another trait. Here Greet requires Named:

pub trait Named {
    fn name(&self) -> String;
}

pub trait Greet: Named {
    fn greet(&self) -> String {
        format!("hi {}", self.name())
    }
}

Named is called a supertrait of Greet. The line pub trait Greet: Named states a requirement: a type may implement Greet only if it also implements Named. In exchange, code written inside Greet, such as the method body above, may call Named's methods on self.

The colon resembles class inheritance, but the meaning is narrower. Greet gains no fields or data from Named, and an impl Greet for T block cannot define or replace Named's methods; name must be written in a separate impl Named for T block. The standard library uses the same kind of requirement in Copy: Clone, Eq: PartialEq and Ord: Eq + PartialOrd.

Default methods

A trait method that has a body, like greet above, is a default method. Each implementing type may either use that body or supply its own method with the same signature. A method without a body, like name, must be written by every implementor.

Because greet is Greet's only method and it has a default, an empty block is a complete implementation of Greet:

impl Greet for Person {}

This block adds a working greet method to Person, provided Person also implements Named.

Your task

pub trait Named { fn name(&self) -> String; }
pub trait Greet: Named { fn greet(&self) -> String { ... } }

pub struct Person(String);
pub struct Robot(String);

pub fn greet_all(specs: Vec<(String, String)>) -> Vec<String>

Each spec is a pair (kind, name). greet_all returns one greeting per spec, in the same order as the input. An empty input returns an empty vector.

  • If kind is exactly "robot", the spec builds a Robot. The comparison is case-sensitive and does no trimming, so "Robot" and " robot" are not robots. A robot's name() returns the name converted with Rust's str::to_uppercase, and Robot replaces the default greet with one that returns "BEEP " followed by name(). For example, ("robot", "hal") produces "BEEP HAL".
  • Any other kind, including the empty string, builds a Person. A person's name() returns the name unchanged, and Person uses the default greet. For example, ("person", "ada") produces "hi ada".

Names may be empty or contain any Unicode text. An empty name still keeps the space: "BEEP " or "hi ". to_uppercase follows Unicode case mapping, so "héllo" becomes "HÉLLO", and the result can be longer than the input (for example, "ß" becomes "SS").

The starter already builds each value as a Box<dyn Greet>, a pointer to a value whose concrete type is chosen at run time, and calls greet through it. You do not need to change greet_all.

Because every Greet type is also a Named type, Named's methods can be called through a dyn Greet as well: who.name() compiles. Since Rust 1.86, a &dyn Greet can also be converted to a &dyn Named.

The compiler error in the starter

The starter implements Greet for both types and Named for neither. Compiling it produces errors like this one (abridged):

error[E0277]: the trait bound `Person: Named` is not satisfied
  |
  | impl Greet for Person {}
  |                ^^^^^^ unsatisfied trait bound
  |
help: the trait `Named` is not implemented for `Person`
note: required by a bound in `Greet`
  |
  | pub trait Greet: Named {
  |                  ^^^^^ required by this bound in `Greet`

The error points at the impl Greet line, but the missing piece is the supertrait: the fix is a new impl Named for Person block, not a change to the impl Greet block. The same E0277 appears for Robot. The compiler also reports E0599 ("no method named name found") inside Robot's greet, because name is not available on a type that does not implement Named. All of these errors go away once both Named implementations exist.

A related error you may meet elsewhere is E0038, "the trait Greet is not dyn compatible". A trait used as dyn Greet must be dyn compatible, and so must its supertraits. If Named gained a generic method, or a method that returns Self, without a where Self: Sized bound on that method, then Box<dyn Greet> would stop compiling even though Greet itself did not change.

Grading

Your code must compile, pass the tests and pass cargo clippy -- -D warnings (Clippy with every warning treated as an error). That includes rustc's dead_code lint, which warns about private items nothing uses. Keep the pub visibility the starter gives the traits and types.