Ein Supertrait ist ein Trait, das ein anderes Trait voraussetzt. Wenn trait Ord: PartialOrd deklariert ist, bedeutet das: jeder Typ, der Ord implementiert, muss auch PartialOrd implementieren. Damit baust du Trait-Hierarchien — schwächere Traits oben, stärkere Traits darunter, mit klaren Anforderungs-Ketten. Die Stdlib nutzt das ausgiebig: Copy: Clone, Eq: PartialEq, Ord: Eq + PartialOrd, Error: Debug + Display. Wer den Mechanismus versteht, kann saubere API-Hierarchien bauen, in denen jeder Trait die schwächere Variante als Basis nutzt.

Syntax

Ein Supertrait wird mit Doppelpunkt am Trait-Namen deklariert.

Rust Erster Supertrait
use std::fmt::Display;

// Sichtbar setzt Display voraus
trait Sichtbar: Display {
    fn render(&self) -> String {
        format!("[Sichtbar] {self}")
    }
}

struct Punkt { x: i32, y: i32 }

// MUSS Display implementieren, weil Sichtbar es voraussetzt
impl Display for Punkt {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl Sichtbar for Punkt {}

fn main() {
    let p = Punkt { x: 3, y: 4 };
    println!("{}", p.render());     // "[Sichtbar] (3, 4)"
}

trait Sichtbar: Display heißt: jeder Typ, der Sichtbar implementieren will, muss zuerst Display implementieren. Im Sichtbar-Body kannst du dann Display-Methoden (hier indirekt via format!("{self}")) nutzen, weil garantiert ist, dass self Display ist.

Wenn du impl Sichtbar for Punkt {} schreibst, ohne dass Punkt Display ist, gibt der Compiler einen Fehler:

error[E0277]: `Punkt` doesn't implement `std::fmt::Display`
note: required by a bound in `Sichtbar`

Mehrere Supertraits

Mit + kombinierst du mehrere Supertraits.

Rust Mehrfach-Supertrait
use std::fmt::{Debug, Display};

// Loggable setzt sowohl Debug als auch Display voraus
trait Loggable: Debug + Display {
    fn log(&self) {
        println!("DEBUG: {self:?}");
        println!("DISPLAY: {self}");
    }
}

struct Wert(i32);

impl Debug for Wert {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Wert({})", self.0)
    }
}
impl Display for Wert {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}
impl Loggable for Wert {}

fn main() {
    Wert(42).log();
    // DEBUG: Wert(42)
    // DISPLAY: 42
}

Mehrere Supertraits sind sehr typisch — eine Trait-Hierarchie sammelt oft mehrere Voraussetzungen.

Supertrait-Methoden im Sub-Trait

Im Body des Sub-Traits kannst du Methoden des Supertraits via self aufrufen — sie sind garantiert verfügbar.

Rust Methoden-Zugriff
trait Counter {
    fn count(&self) -> u32;
}

// ExtendedCounter setzt Counter voraus
trait ExtendedCounter: Counter {
    // Default-Methode nutzt Counter::count
    fn is_zero(&self) -> bool {
        self.count() == 0
    }

    fn is_positive(&self) -> bool {
        self.count() > 0
    }

    fn doubled(&self) -> u32 {
        self.count() * 2
    }
}

struct Counter5;
impl Counter for Counter5 {
    fn count(&self) -> u32 { 5 }
}

// Automatisch: ExtendedCounter funktioniert, wenn Counter funktioniert
impl ExtendedCounter for Counter5 {}

fn main() {
    let c = Counter5;
    assert_eq!(c.count(), 5);
    assert_eq!(c.doubled(), 10);
    assert!(!c.is_zero());
    assert!(c.is_positive());
}

ExtendedCounter-Default-Methoden rufen self.count() auf — die Methode aus Counter. Der Compiler weiß, dass self Counter implementiert, weil ExtendedCounter es als Supertrait fordert.

Stdlib-Hierarchien

Die Stdlib hat einige sehr klare Supertrait-Beispiele.

Rust Stdlib-Auszug
// Klone-Hierarchie
// pub trait Copy: Clone {}
//     → Copy verlangt Clone

// Vergleichs-Hierarchie
// pub trait Eq: PartialEq {}
//     → Eq verlangt PartialEq

// pub trait Ord: Eq + PartialOrd {
//     fn cmp(&self, other: &Self) -> Ordering;
// }
//     → Ord verlangt Eq + PartialOrd

// Error-Hierarchie
// pub trait Error: Debug + Display { ... }
//     → Error verlangt Debug + Display

// Iterator-Adapter mit Supertrait
// pub trait DoubleEndedIterator: Iterator {
//     fn next_back(&mut self) -> Option<Self::Item>;
// }
//     → DoubleEndedIterator verlangt Iterator

Jede dieser Hierarchien folgt einem Muster: schwächere Garantie oben, stärkere unten.

  • PartialEq — manche Werte sind vergleichbar (NaN ist kein Wert vergleichbar mit sich selbst)
  • Eq — alle Werte sind vergleichbar (Reflexivität gilt)
  • PartialOrd — Werte haben Reihenfolge, aber nicht alle Paare
  • Ord — Total-Ordnung über alle Wert-Paare

Die stärkere Garantie verlangt die schwächere als Voraussetzung — das ist semantisch sinnvoll. Ein Typ kann nicht "total geordnet" sein, ohne "partiell geordnet" zu sein.

Supertrait ≠ OOP-Vererbung

Auch wenn die Syntax an Vererbung erinnert, sind Supertraits keine Vererbung:

Rust Was Supertraits NICHT sind
trait Tier {
    fn name(&self) -> &str;
}

// Hund ist KEIN Subtyp von Tier — es ist ein eigener Typ
// mit einem zusätzlichen Trait
trait Hund: Tier {
    fn rasse(&self) -> &str;
}

struct Bello { rasse_name: String }

impl Tier for Bello {
    fn name(&self) -> &str { "Bello" }
}

impl Hund for Bello {
    fn rasse(&self) -> &str { &self.rasse_name }
}

// KEINE Subtyp-Beziehung: Bello hat Tier UND Hund implementiert,
// aber Bello ist nicht "abgeleitet von" irgendwas.

Was Supertraits leisten:

  • Voraussetzungs-Beziehung zwischen Traits
  • Garantie für den Sub-Trait, dass Supertrait-Methoden verfügbar sind
  • Logische Hierarchie der Anforderungen

Was sie nicht leisten:

  • Keine "is-a"-Beziehung wie in OOP
  • Kein gemeinsamer Speicher-Layout
  • Keine automatische Vererbung von Method-Bodies (außer expliziten Default-Methoden)
  • Kein Polymorphismus über Typen (das machen dyn Trait und Generics)

Supertraits in Trait-Objekten

Wenn ein Trait dyn Trait werden soll und einen Supertrait hat, ist die Sache subtil: &dyn Sub kann nicht automatisch in &dyn Super konvertiert werden, auch wenn jeder Sub-Typ auch Super implementiert.

Rust Trait-Objekt-Cast
trait Tier {
    fn name(&self) -> String;
}

trait Hund: Tier {
    fn bellen(&self);
}

struct Bello;
impl Tier for Bello {
    fn name(&self) -> String { String::from("Bello") }
}
impl Hund for Bello {
    fn bellen(&self) { println!("Wuff!"); }
}

fn main() {
    let bello = Bello;

    // OK — direkt in &dyn Hund
    let hund_obj: &dyn Hund = &bello;
    hund_obj.bellen();
    // hund_obj.name();   // OK in neueren Rust-Versionen (Supertrait-Upcasting)

    // Direkter Tier-View
    let tier_obj: &dyn Tier = &bello;
    println!("{}", tier_obj.name());
}

Stand 2026 (Rust 1.86+): Supertrait-Upcasting ist stabilisiert — &dyn Hund kann zu &dyn Tier gecasted werden, weil die Vtable die Supertrait-Methoden mitführt. In älteren Rust-Versionen musste man manuelle Wrapper bauen.

Praxis: Supertraits im echten Code

Hierarchische Validatoren

Rust Validator-Hierarchie
pub trait Validator {
    fn validate(&self, input: &str) -> bool;
}

pub trait DetailedValidator: Validator {
    fn validate_with_reason(&self, input: &str) -> Result<(), String>;

    // Default: Default-Validate aus dem Result ableiten
    fn validate(&self, input: &str) -> bool {
        self.validate_with_reason(input).is_ok()
    }
}

struct LengthValidator { min: usize, max: usize }

impl Validator for LengthValidator {
    fn validate(&self, input: &str) -> bool {
        let len = input.len();
        len >= self.min && len <= self.max
    }
}

impl DetailedValidator for LengthValidator {
    fn validate_with_reason(&self, input: &str) -> Result<(), String> {
        let len = input.len();
        if len < self.min {
            Err(format!("zu kurz: {len} < {}", self.min))
        } else if len > self.max {
            Err(format!("zu lang: {len} > {}", self.max))
        } else {
            Ok(())
        }
    }
}

Zwei Trait-Ebenen: Validator mit einfacher Bool-Antwort, DetailedValidator mit detaillierter Fehler-Erklärung. Der Sub-Trait fordert den Super-Trait und erweitert die API.

Persistierbar mit Display-Anforderung

Rust Persistierbar
use std::fmt::Display;

pub trait Persistierbar: Display {
    fn save_to(&self, path: &str) -> std::io::Result<()> {
        std::fs::write(path, format!("{self}"))
    }
}

struct Config { value: String }

impl Display for Config {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "value={}", self.value)
    }
}

impl Persistierbar for Config {}

fn main() -> std::io::Result<()> {
    let c = Config { value: String::from("hello") };
    c.save_to("/tmp/config.txt")?;
    Ok(())
}

Persistierbar setzt Display voraus, um Werte als String darzustellen. Implementierer brauchen nur Display zu liefern — die Save-Logik kommt gratis.

Geometrie-Hierarchie

Rust Geometrie
pub trait Form {
    fn flaeche(&self) -> f64;
}

pub trait FormMitUmfang: Form {
    fn umfang(&self) -> f64;

    fn flaeche_zu_umfang(&self) -> f64 {
        self.flaeche() / self.umfang()
    }
}

struct Kreis { radius: f64 }

impl Form for Kreis {
    fn flaeche(&self) -> f64 {
        std::f64::consts::PI * self.radius.powi(2)
    }
}

impl FormMitUmfang for Kreis {
    fn umfang(&self) -> f64 {
        2.0 * std::f64::consts::PI * self.radius
    }
}

Trait-Hierarchie für geometrische Formen: alle Formen haben Fläche, manche haben zusätzlich Umfang. Default-Methode nutzt beide Operationen.

Error-Hierarchie

Rust Custom-Error
use std::fmt::{self, Debug, Display};

pub trait MyError: Debug + Display {
    fn code(&self) -> u32;
    fn cause(&self) -> Option<&dyn MyError> { None }
}

#[derive(Debug)]
struct NotFoundError { resource: String }

impl Display for NotFoundError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Ressource nicht gefunden: {}", self.resource)
    }
}

impl MyError for NotFoundError {
    fn code(&self) -> u32 { 404 }
}

Custom-Error-Trait mit Debug + Display als Supertraits — identisch zur Stdlib-Konvention für std::error::Error.

Service mit Cancel

Rust Cancelable Service
pub trait Service {
    fn execute(&mut self);
}

pub trait CancelableService: Service {
    fn cancel(&mut self);

    fn execute_with_timeout(&mut self, timeout_ms: u64) {
        println!("Start service (timeout {timeout_ms}ms)");
        self.execute();
        // In Real-World: timer-Logik mit potentiellem cancel()
    }
}

struct DataService { running: bool }

impl Service for DataService {
    fn execute(&mut self) {
        self.running = true;
        println!("Daten werden verarbeitet");
    }
}

impl CancelableService for DataService {
    fn cancel(&mut self) {
        self.running = false;
        println!("Service abgebrochen");
    }
}

CancelableService erweitert Service um eine Cancel-Operation. Die Sub-Trait-Default execute_with_timeout kombiniert beide Methoden.

Lifecycle-Hierarchie

Rust Lifecycle
pub trait Startable {
    fn start(&mut self);
}

pub trait Stoppable {
    fn stop(&mut self);
}

// Restartable setzt BEIDE voraus
pub trait Restartable: Startable + Stoppable {
    fn restart(&mut self) {
        self.stop();
        self.start();
    }
}

struct Server { running: bool }

impl Startable for Server {
    fn start(&mut self) {
        self.running = true;
        println!("Server gestartet");
    }
}

impl Stoppable for Server {
    fn stop(&mut self) {
        self.running = false;
        println!("Server gestoppt");
    }
}

impl Restartable for Server {}

Restartable kombiniert Startable und Stoppable als Supertraits und bietet eine Default-Methode, die beide nutzt. Klassisches Pattern für Lifecycle-orientierte Komponenten.

Iterator-Erweiterung

Rust Erweiterte Iteratoren
// Custom-Trait, der Iterator erweitert
pub trait CountingIterator: Iterator {
    fn count_and_collect(self) -> (usize, Vec<Self::Item>)
    where Self: Sized
    {
        let v: Vec<Self::Item> = self.collect();
        let c = v.len();
        (c, v)
    }
}

// Blanket-Implementation: alles, was Iterator ist, ist auch CountingIterator
impl<I: Iterator> CountingIterator for I {}

fn main() {
    let (c, v) = (1..=5).count_and_collect();
    assert_eq!(c, 5);
    assert_eq!(v, vec![1, 2, 3, 4, 5]);
}

Trait-Extension-Pattern: ein Supertrait-basierter Sub-Trait, plus Blanket-Impl für alle Iteratoren. So fügst du jedem Iterator-Typ neue Methoden hinzu, ohne die Stdlib zu ändern.

Interessantes

Supertrait = vorausgesetzter Trait.

trait Sub: Super heißt: jeder Typ, der Sub implementiert, muss zuerst Super implementieren. Garantie für den Compiler und für Sub-Methoden, die Super-Methoden nutzen.

Mehrere Supertraits — kombiniert mit +.

trait Foo: Bar + Baz setzt sowohl Bar als auch Baz voraus. Im Foo-Body sind alle Methoden von Bar UND Baz auf self verfügbar.

Stdlib-Hierarchien — semantisch sinnvoll.

Copy: Clone, Eq: PartialEq, Ord: Eq + PartialOrd, Error: Debug + Display. Stärkere Garantien bauen auf schwächeren auf.

Kein OOP-Vererbungs-Konzept.

Supertraits sind Anforderungs-Ketten, keine Subtyp-Beziehungen. Es gibt keine geteilten Speicher-Layouts, keine automatische Method-Vererbung außer expliziten Default-Methoden.

Sub-Trait-Default-Methoden dürfen Super-Methoden aufrufen.

Im Sub-Trait-Body ist garantiert, dass self alle Super-Methoden hat. Damit kannst du in Defaults beliebig die Super-Methoden kombinieren.

Supertrait-Upcasting für dyn (ab Rust 1.86).

&dyn Sub lässt sich seit dieser Version automatisch zu &dyn Super casten. Die Vtable führt Supertrait-Methoden mit. In älteren Versionen mussten Wrapper-Methoden manuell gebaut werden.

Mehrere Supertraits — typisches Pattern.

Ein Trait mit zwei oder drei Supertraits ist nichts Ungewöhnliches. Schau Ord (drei: Eq, PartialOrd, plus implizit PartialEq) — saubere Trennung der Anforderungen.

Blanket-Impl + Supertrait = Trait-Extension-Pattern.

impl<I: Iterator> MyExtension for I {} fügt allen Iteratoren neue Methoden hinzu. Bekannte Stdlib-Variante: IteratorExt-Crates.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Traits

Zur Übersicht