Menu
Coddy logo textTech

Dynamischer Dispatch

Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 45 von 61.

Wenn Sie Generics mit Trait-Bounds wie <T: Summary> verwenden, bestimmt Rust den exakten Typ zur Kompilierzeit. Dies wird als statischer Dispatch bezeichnet – der Compiler generiert spezialisierten Code für jeden konkreten Typ, den Sie verwenden. Es ist schnell, aber es gibt eine Einschränkung: Eine Variable kann nur einen spezifischen Typ halten.

Was ist, wenn Sie eine einzelne Variable benötigen, die zur Laufzeit verschiedene Typen enthalten kann? Hier kommen Trait-Objekte ins Spiel. Mit Box<dyn Trait> können Sie jeden Typ speichern, der das Trait implementiert:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

Das Schlüsselwort dyn zeigt dynamischen Dispatch an – Rust schlägt erst zur Laufzeit und nicht zur Kompilierzeit nach, welche Methode aufgerufen werden soll. Das Box ist notwendig, da Trait-Objekte keine bekannte Größe haben; Box stellt einen Pointer mit einer festen Größe bereit.

Stellen Sie es sich so vor: Generics sagen „Ich arbeite mit Typ T“, während Trait-Objekte sagen „Ich arbeite mit allem, was dies tun kann“. Der Kompromiss ist ein geringer Laufzeitaufwand für die Flexibilität, verschiedene Typen über dieselbe Variable zu handhaben.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Fahrzeugvermietungssystem bauen, das die Leistungsfähigkeit des dynamischen Dispatch (Dynamic Dispatch) demonstriert! Sie werden ein Trait erstellen, das definiert, was jedes mietbare Fahrzeug tun kann, und dann Box<dyn Trait> verwenden, um verschiedene Fahrzeugtypen in derselben Variable zu speichern.

Sie werden Ihren Code in zwei Dateien organisieren:

  • vehicle.rs: Definieren Sie ein öffentliches Rentable Trait mit einer rental_info Methode, die &self entgegennimmt und einen String zurückgibt. Erstellen Sie dann zwei öffentliche Structs:
    • Car — mit einem öffentlichen model Feld (String). Seine rental_info sollte "Car: {model}" zurückgeben.
    • Bike — mit einem öffentlichen brand Feld (String). Seine rental_info sollte "Bike: {brand}" zurückgeben.
  • main.rs: Binden Sie Ihr vehicle-Modul ein und nutzen Sie die Eingaben, um Dynamic Dispatch zu demonstrieren. Erstellen Sie eine Box<dyn Rentable> Variable, die zuerst ein Car hält, geben Sie dessen Mietinformationen aus, weisen Sie dann derselben Variable ein Bike zu und geben Sie dessen Mietinformationen erneut aus.

Das Magische hierbei ist, dass eine einzige Variable vom Typ Box<dyn Rentable> entweder ein Car oder ein Bike halten kann — Rust findet zur Laufzeit heraus, welche rental_info Methode aufgerufen werden muss. Das ist Dynamic Dispatch in Aktion!

Ihre Ausgabe sollte die Informationen beider Fahrzeuge anzeigen:

Car: {model}
Bike: {brand}

Zum Beispiel mit den Eingaben "Tesla Model 3" und "Trek":

Car: Tesla Model 3
Bike: Trek

Sie erhalten zwei Eingaben: das Automodell und die Fahrradmarke.

Spickzettel

Rust verwendet statisches Dispatching mit Generics – der Compiler generiert zur Kompilierzeit spezialisierten Code für jeden Typ. Dies ist schnell, bedeutet aber, dass eine Variable nur einen bestimmten Typ halten kann.

Trait-Objekte ermöglichen dynamisches Dispatching, wodurch eine einzelne Variable zur Laufzeit verschiedene Typen halten kann. Verwenden Sie Box<dyn Trait>, um jeden Typ zu speichern, der das Trait implementiert:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

Das Schlüsselwort dyn zeigt dynamisches Dispatching an – Methodenaufrufe werden zur Laufzeit aufgelöst. Box ist erforderlich, da Trait-Objekte zur Kompilierzeit keine bekannte Größe haben.

Hauptunterschied: Generics sagen „Ich arbeite mit Typ T“ (einem spezifischen Typ), während Trait-Objekte sagen „Ich arbeite mit allem, was dieses Trait implementiert“ (mehrere Typen über dieselbe Variable). Der Kompromiss ist ein geringer Laufzeitaufwand für mehr Flexibilität.

Probier es selbst

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // Eingabe lesen
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: Erstelle eine Box<dyn Rentable> Variable, die ein Car hält
    // Gib die Mietinformationen mit der Methode rental_info() aus
    
    // TODO: Weise derselben Variable ein Bike zu
    // Gib die Mietinformationen erneut aus
    
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Object Oriented Programming