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.
DisplayandShoutare 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 theDisplayimpl; the compiler checks the bound, not the sequence. - Several are joined with
+—trait Broadcast: Shout + Clone— exactly as in awhereclause.
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¶
- What a trait is — default bodies, which are what a supertrait makes worth writing
- A trait must be in scope —
E0599, the other missing-method error, and how to tell them apart - Static vs dynamic dispatch — the vtable that carries the supertrait's methods
- Debug and Display — the supertrait used above, and why it cannot be derived
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