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.
Aufgabe
EinfachLassen 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 öffentlichesRentableTrait mit einerrental_infoMethode, die&selfentgegennimmt und einenStringzurückgibt. Erstellen Sie dann zwei öffentliche Structs:Car— mit einem öffentlichenmodelFeld (String). Seinerental_infosollte"Car: {model}"zurückgeben.Bike— mit einem öffentlichenbrandFeld (String). Seinerental_infosollte"Bike: {brand}"zurückgeben.
main.rs: Binden Sie Ihr vehicle-Modul ein und nutzen Sie die Eingaben, um Dynamic Dispatch zu demonstrieren. Erstellen Sie eineBox<dyn Rentable>Variable, die zuerst einCarhält, geben Sie dessen Mietinformationen aus, weisen Sie dann derselben Variable einBikezu 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: TrekSie 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
}
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
1Methoden und Verhalten
Einführung in ImplementierungsblöckeDer Self-ParameterVeränderliche MethodenAssoziierte FunktionenMehrere ImplementierungsblöckeMethod ChainingRückblick – Rechteck-Aktionen4Projekt: Virtuelles Haustier
Das Haustier definierenDas Haustier füttern7Standard-Traits
Das Debug-TraitDas Display-TraitClone und CopyGleichheits-TraitsZusammenfassung – Druckbarer Punkt10Projekt: Dokumentensystem
Das Draw-TraitText-Komponente2Kapselung und Module
Grundlagen der ModuleDas Public-SchlüsselwortPrivate FelderGetterSetterWiederholung – Sicherer Tresor5Generics
Generische StructsGenerische MethodenMehrere generische TypenGenerische FunktionenZusammenfassung - Koordinatenpunkt8Traits als Einschränkungen
Syntax für Trait BoundsMehrfache BoundsDie Where-KlauselTypen mit Traits zurückgebenRückblick – Generischer Drucker11Design Patterns in Rust
Newtype-PatternKompositionDas Drop-TraitFrom und IntoZusammenfassung – Smart Pointer Mock3Fortgeschrittene Enums
Enums mit DatenMethoden für EnumsMatching von DatenvariantenDas Option-Enum erneut betrachtetZusammenfassung – Shape-Enum6Definition von Traits
Was ist ein Trait?Traits implementierenStandardimplementierungenStandardimplementierungen überschreibenTraits mit ParameternZusammenfassung – Media Player9Trait-Objekte
Dynamischer DispatchVektoren von TraitsIterieren über Trait-ObjekteRückblick – Zoo-Manager