Dynamischer Dispatch
Teil des Abschnitts Objektorientierte Programmierung der Rust-Journey von Coddy. Lektion 45 von 61.
Wenn du Generics mit Trait-Beschränkungen wie <T: Summary> verwendest, bestimmt Rust den genauen Typ zur Kompilierzeit. Dies wird als statischer Dispatch bezeichnet: Der Compiler generiert spezialisierten Code für jeden konkreten Typ, den du verwendest. Das ist schnell, aber es gibt eine Einschränkung: Eine Variable kann nur einen bestimmten Typ enthalten.
Was ist, wenn du eine einzelne Variable benötigst, die zur Laufzeit verschiedene Typen aufnehmen kann? Hier kommen Trait-Objekte ins Spiel. Mit Box<dyn Trait> kannst du 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 kennzeichnet den dynamischen Dispatch: Rust ermittelt zur Laufzeit statt zur Kompilierungszeit, welche Methode aufgerufen werden soll. Box ist erforderlich, weil Trait-Objekte keine bekannte Größe haben; Box stellt einen Zeiger mit fester Größe bereit.
Stell es dir so vor: Generics sagen „Ich arbeite mit dem Typ T“, während Trait-Objekte sagen: „Ich arbeite mit allem, was dies tun kann.“ Der Kompromiss besteht in geringen Laufzeitkosten für die Flexibilität, verschiedene Typen über dieselbe Variable zu verarbeiten.
Aufgabe
EinfachLass uns ein Fahrzeugverleihsystem erstellen, das die Leistungsfähigkeit des dynamischen Dispatching demonstriert! Du wirst ein Trait erstellen, das definiert, was jedes mietbare Fahrzeug tun kann, und anschließend Box<dyn Trait> verwenden, um verschiedene Fahrzeugtypen in derselben Variable zu speichern.
Du wirst deinen Code auf zwei Dateien verteilen:
vehicle.rs: Definiere ein öffentlichesRentable-Trait mit einerrental_info-Methode, die&selfentgegennimmt und einenStringzurückgibt. Erstelle anschließend zwei öffentliche Strukturen:Car: mit einem öffentlichenmodel-Feld (String). Die Methoderental_infosollCar: {model}zurückgeben.Bike: mit einem öffentlichenbrand-Feld (String). Die Methoderental_infosollBike: {brand}zurückgeben.
main.rs: Binde dein Fahrzeugmodul ein und verwende die Eingaben, um dynamisches Dispatching zu demonstrieren. Erstelle eine Variable vom TypBox<dyn Rentable>, die zunächst einCarenthält, gib dessen Mietinformationen aus, weise derselben Variable anschließend einBikezu und gib dessen Mietinformationen erneut aus.
Das Faszinierende daran ist, dass eine einzelne Variable vom Typ Box<dyn Rentable> entweder ein Car oder ein Bike enthalten kann: Rust ermittelt zur Laufzeit, welche rental_info-Methode aufgerufen werden soll. Das ist dynamisches Dispatching in Aktion!
Deine 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: TrekDu erhältst zwei Eingaben: das Automodell und die Fahrradmarke.
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 rental_info()-Methode aus
// TODO: Weise dieselbe Variable neu zu, sodass sie ein Bike hält
// 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 Objektorientierte Programmierung
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Übe selbstständig: Online-Rust-Compiler