Skip to content

Modul 8 — Traits & Generics

Abstraktion ohne Kosten: generische Funktionen und Typen, eigene Traits mit Default-Methoden, die Std-Traits, die jeder kennt.

Fortschritt0/4 Lektionen
29

Generics: Typen als Parameter

Lektion 29 von 4

Eine Funktion für jede Typvariante? largest_i32, largest_char, largest_string — dreimal derselbe Code. Generics machen den Typ zum Parameter. Aber Achtung: ohne Zusagen kompiliert nicht einmal ein Vergleich:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in list {
        if item > max {
            max = item;
        }
    }
    max
}

fn main() {
    let zahlen = vec![3, 9, 4];
    let zeichen = vec!['y', 'a', 'm'];
    println!("{}", largest(&zahlen));
    println!("{}", largest(&zeichen));
}

Das : PartialOrd ist ein Trait-Bound — eine Zusage an den Compiler: "jedes T, mit dem du diese Funktion instanziierst, kann verglichen werden". Ohne den Bound würde die Zeile item > max den Compiler stoppen: er kennt die Fähigkeiten von T nicht.

Auch Structs können generisch sein — mit so vielen Typparametern wie Felder brauchen:

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let ganzzahlig = Point { x: 3, y: -2 };
    let gemischt = Point { x: 1, y: 0.5 };
    println!("{:?} und {:?}", ganzzahlig, gemischt);
}

Der Typ von gemischt wird inferiert — die Parameter T und U füllen sich automatisch. Und das Beste: Generics kosten nichts zur Laufzeit. Der Compiler erzeugt pro verwendetem konkreten Typ eine spezialisierte Kopie (Monomorphisierung) — derselbe Maschinencode, als hättest du jede Funktion von Hand geschrieben.

Monomorphisierung vs. dynamischer Dispatch

Monomorphisierung ist statischer Dispatch: der Compiler erzeugt für jeden konkreten Typ eine eigene Funktion — zur Laufzeit gibt es keine Tabelle, keinen Indirektion. Das ist der Standardweg in Rust.

Die Alternative ist dynamischer Dispatch mit dyn Trait: eine VTable (virtuelle Methodentabelle) zur Laufzeit. Das kostet einen Indirection-Sprung, aber der Binary wird nur eine Kopie der Funktion eingebaut — egal wie viele Typen verwendet werden.

// Statisch (Monomorphisierung) — null Kosten, größere Binary
fn print_all<T: Display>(items: &[T]) { ... }

// Dynamisch — eine VTable, kleinere Binary, minimaler Laufzeitkosten
fn print_all(items: &[Box<dyn Display>]) { ... }

Aktuelle Forschung zur Meta-Monomorphisierung (2026) untersucht, wie Compiler spezialisierte Code-Pfade noch aggressiver optimieren können — mit dem Ziel, den Kompromiss zwischen Binary-Größe und Geschwindigkeit weiter zu verschieben.

Übung: der Bound fehlt

🧩 Übung m8-l1-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

30

Traits: gemeinsames Verhalten

Lektion 30 von 4

Generics sagen "irgendein Typ". Traits sagen "irgendein Typ, der DIES kann" — und definieren, was DIES bedeutet. Ein Trait ist ein Vertrag aus Methodensignaturen:

trait Summarize {
    fn author(&self) -> &str;
    fn title(&self) -> &str;

    fn summary(&self) -> String {
        format!("{} von {}", self.title(), self.author())
    }
}

struct Article {
    title: String,
    author: String,
}

impl Summarize for Article {
    fn author(&self) -> &str {
        &self.author
    }
    fn title(&self) -> &str {
        &self.title
    }
}

fn notify(item: &impl Summarize) {
    println!("Neu: {}", item.summary());
}

fn main() {
    let a = Article {
        title: String::from("Rust im Browser"),
        author: String::from("Tobias"),
    };
    notify(&a);
}

Drei Konzepte in einem Beispiel: summary hat einen Rumpf — eine Default-Implementierung, die Article erbt, ohne sie zu schreiben. impl Summarize for Article ist die Implementierung des Vertrags. Und &impl Summarize in notify akzeptiert jeden Typ, der den Vertrag erfüllt — Article heute, ein Tweet morgen.

Trait-WerkstattBereit
Editor wird geladen…

Trait-Bounds verbinden Generics mit Verhalten

  • fn notify(item: &impl Summarize) — kurzform für einen generischen Parameter
  • Bounds dokumentieren: was die Funktion wirklich vom Typ braucht
  • Default-Methoden machen Traits erweiterbar, ohne alle Implementierer zu brechen

Übung: der fehlende Vertrag

🧩 Übung m8-l2-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

31

Die Std-Traits, die jeder kennt

Lektion 31 von 4

Vieles vom Standardverhalten kommt aus Standard-Traits — meist per derive, manchmal von Hand:

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Money {
    euros: u32,
    cents: u8,
}

fn main() {
    let preis = Money { euros: 12, cents: 34 };
    let rabatt = Money { euros: 9, cents: 99 };
    println!("{preis:?}");
    println!("teurer: {}", preis > rabatt);
}

Was jeder einzelne bringt:

Traitgibtper derive?
Debug{:?}-Ausgabeja
Clone / Copyduplizieren / kopieren beim Zuweisenja
PartialEq / Eq== und !=ja
PartialOrd / Ord<, >, sortierenja
Default::default() mit Nullwertenja
Hashals HashMap-Schlüsselja
Display{}-Ausgabe für Nutzervon Hand

Display zeigt, warum "von Hand": Nutzerausgabe will FORMAT — Währung, Dezimalstellen, Locale. Das kann kein derive erraten:

use std::fmt;

impl fmt::Display for Money {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{},{:02} €", self.euros, self.cents)
    }
}

fn main() {
    let m = Money { euros: 12, cents: 4 };
    println!("{m}");
}

Und From/Into — das Modul-7-Brücke-Werkzeug: implementierst du From, bekommst du Into gratis dazu.

Übung: Money komplett machen

🧩 Übung m8-l3-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

32

Mini-Projekt: stats-lib

Lektion 32 von 4

Zeit für ein Mini-Projekt mit Produktqualität: eine kleine Statistik- Bibliothek. Der Clou ist die API-Entscheidung: Mittelwert und Median geben ein Option zurück — denn eine leere Datenreihe hat keinen Mittelwert.

Praxis-Szenario: Die Auswertung, die lügt

Dein Team analysiert Messreihen. Ein Kollege schrieb eine mean-Funktion, die bei leerer Eingabe 0.0 zurückgibt. Folge: Dashboards zeigen plötzlich 'Durchschnitt 0 ms' — und niemand merkt, dass die Datenquelle leer war. Der Fehler saß nicht in der Berechnung, sondern in der API: sie hatte keinen Weg zu sagen 'kein Ergebnis'.

🧩 Übung m8-l4-e1

🔮 Vorhersage: Was passiert beim Ausführen?

Editor wird geladen…
KI-Tutor (sokratisch — keine Komplettlösungen)

Was du in Modul 8 geschafft hast

  • Generics mit Bounds: eine Funktion statt vier Kopien — gratis durch Monomorphisierung
  • Eigene Traits mit Default-Methoden; &impl Trait als offener Parameter
  • Die Std-Traits: Debug/Clone/Copy/Vergleich per derive, Display/From von Hand
  • Eine API, die 'kein Ergebnis' als Wert ausdrückt — statt zu lügen

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

Modul-Checkpoint

Checkpoint: Modul 8

Bestehensgrenze: 80 %. Der Versuch wird bewertet und zählt für das Modul-Gate.

This site uses essential cookies for Stripe payments. No tracking cookies.