Vektoren von Traits
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 46 von 61.
Sie haben gesehen, wie Box<dyn Trait> einer einzelnen Variablen ermöglicht, verschiedene Typen zu halten. Die wahre Stärke zeigt sich, wenn Sie dies mit Collections kombinieren – indem Sie mehrere verschiedene Typen in einem einzigen Vektor speichern.
Ein regulärer Vektor erfordert, dass alle Elemente vom gleichen Typ sind. Man kann keinen Vec<Dog> haben, der auch Katzen enthält. Aber mit Trait-Objekten können Sie einen Vektor erstellen, der alles enthält, was ein gemeinsames Trait implementiert:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
Der Typ Vec<Box<dyn Speak>> bedeutet „ein Vektor von Boxed-Trait-Objekten“. Jedes Element ist eine Box, die auf etwas zeigt, das Speak implementiert – die tatsächlichen Typen können variieren. Man umschließt jeden Wert mit Box::new(), wenn man ihn zum Vektor hinzufügt.
Dieses Muster ist essenziell für den Aufbau flexibler Systeme, in denen Sie Sammlungen von verwandten, aber unterschiedlichen Objekten verwalten müssen – wie UI-Komponenten, Spielentitäten oder Plugin-Systeme.
Aufgabe
EinfachLassen Sie uns ein Benachrichtigungssystem bauen, das verschiedene Arten von Alarmen verarbeiten kann! Sie werden eine Sammlung erstellen, die verschiedene Benachrichtigungstypen speichert – E-Mails, SMS-Nachrichten und Push-Benachrichtigungen – alles in einem einzigen Vektor unter Verwendung von Trait-Objekten.
Sie werden Ihren Code über zwei Dateien organisieren:
notifications.rs: Definieren Sie ein öffentlichesNotify-Trait mit einersend-Methode, die&selfentgegennimmt und einenStringzurückgibt, der die gesendete Benachrichtigung beschreibt. Erstellen Sie dann drei öffentliche Structs:Email— mit einem öffentlichenrecipient-Feld (String). Diesend-Methode sollte"Email to: {recipient}"zurückgeben.Sms— mit einem öffentlichenphone-Feld (String). Diesend-Methode sollte"SMS to: {phone}"zurückgeben.Push— mit einem öffentlichendevice-Feld (String). Diesend-Methode sollte"Push to: {device}"zurückgeben.
main.rs: Binden Sie Ihr Benachrichtigungsmodul ein und erstellen Sie einen Vektor vom TypVec<Box<dyn Notify>>, der alle drei Benachrichtigungstypen enthält. Verwenden Sie die bereitgestellten Eingaben, um jeweils einen Benachrichtigungstyp zu erstellen, fügen Sie diese in der Reihenfolge (Email, Sms, Push) Ihrem Vektor hinzu und geben Sie dann das Ergebnis des Aufrufs vonsend()für das erste Element des Vektors aus.
Das Schlüsselkonzept hierbei ist, dass Email, Sms und Push, obwohl sie völlig unterschiedliche Structs sind, alle zusammen im selben Vektor existieren können, da sie das Notify-Trait gemeinsam haben. Jedes Element wird mit Box::new() umschlossen, um das Trait-Objekt zu erstellen.
Ihre Ausgabe sollte die erste Benachrichtigung zeigen:
Email to: {recipient}Zum Beispiel mit den Eingaben "alice@example.com", "555-1234" und "iPhone-12":
Email to: alice@example.comSie erhalten drei Eingaben: den E-Mail-Empfänger, die Telefonnummer und den Gerätenamen.
Spickzettel
Ein Vec<Box<dyn Trait>> ermöglicht das Speichern mehrerer verschiedener Typen in einem einzigen Vektor, solange sie alle dasselbe Trait implementieren.
Reguläre Vektoren erfordern, dass alle Elemente vom gleichen Typ sind, aber Trait-Objekte ermöglichen heterogene Sammlungen:
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 animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
];
}
Jedes Element wird beim Hinzufügen zum Vektor mit Box::new() umschlossen. Die tatsächlichen Typen können sich unterscheiden, solange sie das gemeinsame Trait implementieren.
Probier es selbst
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Eingaben lesen
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: Erstelle einen Vec<Box<dyn Notify>>, um verschiedene Benachrichtigungstypen zu speichern
// TODO: Erstelle Email, Sms und Push Instanzen unter Verwendung der Eingaben
// TODO: Füge sie der Reihe nach (Email, Sms, Push) mit Box::new() zum Vektor hinzu
// TODO: Gib das Ergebnis des Aufrufs von send() für das erste Element 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