🎓 A1Learner
🔍

Rust Supertraits

A supertrait is a trait that another trait requires. The requirement is written with a colon, like trait Student: Person.

Read the colon as "must also be"

The trait after the colon is the supertrait — a Student must also be a Person:

trait Student: Person {
    fn university(&self) -> String;
}

Demand several with +

A plus asks for more than one supertrait — and Student brings Person along with it:

trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}

Call the whole chain

Code holding a CompSciStudent may call every level — its own method and each supertrait's:

fn hello(s: &dyn CompSciStudent) {
    println!("{} codes in {}", s.name(), s.fav_language());
}

Default methods may spend the supertrait’s promises

A subtrait’s default method can call supertrait methods directly — the requirement guarantees they exist. (Run in a harness: a Cat named Miso prints “pet: Miso”.)

trait Pet: Named {
    fn tag(&self) -> String {
        format!("pet: {}", self.name())
    }
}

One trait object, the whole chain

Name one trait in the box and the supertrait methods ride along — both calls work on the same value:

let pet: Box<dyn Pet> = Box::new(cat);
println!("{} / {}", pet.name(), pet.tag());

Skip the supertrait and it will not build

Implementing Student without Person is refused — the requirement is part of the trait itself:

impl Student for CSStudent {
    // error E0277: the trait bound `CSStudent: Person` is not satisfied
}

Example

trait Person { fn name(&self) -> String; }
trait Student: Person { fn university(&self) -> String; }
trait Programmer { fn fav_language(&self) -> String; }
trait CompSciStudent: Programmer + Student { fn git_username(&self) -> String; }
struct CSStudent { name: String, university: String, fav_language: String, git_username: String }
impl Person for CSStudent { fn name(&self) -> String { self.name.clone() } }
impl Student for CSStudent { fn university(&self) -> String { self.university.clone() } }
impl Programmer for CSStudent { fn fav_language(&self) -> String { self.fav_language.clone() } }
impl CompSciStudent for CSStudent { fn git_username(&self) -> String { self.git_username.clone() } }
fn greeting(student: &dyn CompSciStudent) -> String {
    format!("{} studies at {}, codes in {}, git: {}", student.name(), student.university(), student.fav_language(), student.git_username())
}
fn main() {
    let s = CSStudent { name: String::from("Alice"), university: String::from("MIT"), fav_language: String::from("Rust"), git_username: String::from("alice_codes") };
    println!("{}", greeting(&s));
}

Key points

  • Standard traits stack this way: Copy: Clone, and Ord: Eq + PartialOrd.
  • The Derivable Traits lesson showed these labels as supertrait chains.
  • A trait object names exactly one non-auto trait.
  • dyn Pet + Send is legal.
  • dyn Pet + Named is not written.
  • Named is already there as the supertrait.

In this example, Student demands Person. CompSciStudent demands Programmer plus Student. So Person is required as well, one level down. CSStudent therefore needs four impl blocks, one per trait. The greeting function takes a &dyn CompSciStudent and calls all four methods. name comes from Person. university comes from Student. fav_language comes from Programmer. git_username comes from CompSciStudent itself. The one printed line uses every level of the stack.

Note

A supertrait is a requirement, not a parent class.

No fields or data are inherited.

Only the promise that the other trait is implemented too.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

trait Student: Person — what must a type do to implement Student?