Skip to content

Supertraits

Level: 201 · working knowledge

One line: trait Shout: Display does not mean Shout inherits from Display — it means anything implementing Shout must implement Display too, and in exchange Shout's own default bodies may use it.

use std::fmt;

trait Shout: fmt::Display {
    fn shout(&self) -> String { format!("{}!!!", self.to_string().to_uppercase()) }
}

struct Dog;
impl fmt::Display for Dog {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "woof") }
}
impl Shout for Dog {}          // empty — the default body is enough

fn main() {
    println!("{}", Dog.shout());   // WOOF!!!
}

impl Shout for Dog {} is empty, and the interesting work happened in the Display impl above it. That is the trade a supertrait makes: the implementor owes you Display, so the trait can spend it.

It is a bound, not a parent

The colon is the same colon as in fn f<T: Display>. trait Shout: Display is shorthand for "Self must implement Display", and everything that follows from it follows from that one reading:

  • No fields are inherited, because traits have no fields to inherit.
  • Nothing is overridden. Display and Shout are two separate impl blocks that happen to be on the same type.
  • The order does not matter. Write impl Shout for Dog {} above or below the Display impl; the compiler checks the bound, not the sequence.
  • Several are joined with +trait Broadcast: Shout + Clone — exactly as in a where clause.

The error when you forget

Leave out the Display impl and the failure lands on the impl Shout line, not on the call site:

error[E0277]: `Cat` doesn't implement `std::fmt::Display`
  |
6 | impl Shout for Cat {}
  |                ^^^ unsatisfied trait bound
  |
help: the trait `std::fmt::Display` is not implemented for `Cat`
note: required by a bound in `Shout`
  |
2 | trait Shout: fmt::Display {
  |              ^^^^^^^^^^^^ required by this bound in `Shout`

Worth reading closely, because it is a good error: it names the missing trait, the type missing it, and the bound that demanded it. Compare it with the failure mode of a method that simply is not there — E0599, which is what a trait that is not in scope produces instead.

A trait object carries the supertrait too

dyn Shout implements Shout and Display, because the vtable is built knowing the bound:

let obj: &dyn Shout = &Dog;
println!("{}", obj);            // Display, through the trait object
let up: &dyn fmt::Display = obj;  // and it upcasts, since Rust 1.86

That second line is newer than most of the material written about supertraits: trait upcasting — converting &dyn Shout to &dyn Display — was unstable for years and stabilized in Rust 1.86. Verified here on 1.97.1. Anything older than 2025 will tell you it needs nightly.

The verified output

Verified output of supertraits.rs — regenerated by tools/run_examples.py, never hand-typed.

1. The default body could call to_string() because Display is required
   dog.shout()  = WOOF!!!
   dog.twice()  = WOOF!!! ... WOOF!!!

2. Both impl blocks are empty — `impl Shout for Dog {}`
   The work was done by `impl Display for Dog`, which the supertrait
   demanded. Leave that out and the impl is E0277, not E0599:
   the trait bound `Dog: Display` is not satisfied.

3. A trait object carries the supertrait's methods too
   as &dyn Shout, Display still works: woof
   ...and so does the trait's own method: WOOF!!!

4. It is not inheritance: no fields, no override, two separate impls
   size_of::<Dog>() = 0   Shout and Display added nothing to the value
   and the same value viewed as &dyn Display is a different vtable: woof

See also

Po polsku

Nazwa supertrait podsuwa „cechę nadrzędną”, a stąd już tylko krok do dziedziczenia — i to jest dokładnie ta pomyłka, której ta strona ma zapobiec. trait Shout: Display niczego nad niczym nie ustawia: ten dwukropek to ten sam dwukropek, co w fn f<T: Display>, czyli zwykłe ograniczenie o treści „Self musi implementować Display”. Reszta wynika już z tego jednego odczytania — nie ma dziedziczonych pól (cechy nie mają pól), nie ma nadpisywania (to dwa osobne bloki impl na tym samym typie), kolejność zapisu nic nie znaczy, a kilka wymagań łączy się plusem, dokładnie jak w klauzuli where.

Umowa, którą się przy tym zawiera, jest prosta: implementujący jest ci winien Display, więc twoja cecha (trait) może go wydać w swoich domyślnych ciałach metod. Dlatego impl Shout for Dog {} jest puste — cała praca siedzi w sąsiedniej implementacji Display. Gdy się o niej zapomni, błąd wyląduje na linijce impl Shout for Cat {}, a nie w miejscu wywołania, i będzie to E0277 z nazwą brakującej cechy oraz wskazaniem ograniczenia, które jej zażądało. Tę parę warto rozróżniać na pamięć: E0277 mówi „typ nie spełnia ograniczenia”, a E0599 — „takiej metody tu nie ma”, co zwykle znaczy, że cechy nie wprowadzono do zasięgu przez use.

I rzecz, której nie znajdziesz w żadnym materiale starszym niż 2025 rok: rzutowanie w górę obiektów cech (trait upcasting), czyli przejście z &dyn Shout na &dyn Display, było niestabilne przez lata i trafiło do stabilnego Rusta dopiero w wersji 1.86. Każdy poradnik napisany wcześniej — polski czy angielski — powie, że potrzebujesz nightly. Już nie potrzebujesz.

Szukaj po polsku: cecha nadrzędna · ograniczenia typów w Ruscie · rust supertrait · rust E0277 trait bound not satisfied · rust trait upcasting