Vektoren von Traits
Teil des Abschnitts Objektorientierte Programmierung der Rust-Journey von Coddy. Lektion 46 von 61.
Du hast gesehen, wie Box<dyn Trait> es einer einzelnen Variable ermöglicht, verschiedene Typen zu enthalten. Die eigentliche Stärke zeigt sich, wenn du dies mit Sammlungen kombinierst: mehrere verschiedene Typen in einem einzigen Vektor zu speichern.
Ein regulärer Vektor erfordert, dass alle Elemente vom gleichen Typ sind. Du kannst keinen Vec<Dog> haben, der auch Katzen enthält. Mit Trait-Objekten kannst du jedoch 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 aus verpackten Trait-Objekten“. Jedes Element ist ein Box, das auf etwas zeigt, das Speak implementiert: Die tatsächlichen Typen können unterschiedlich sein. Du verpackst jeden Wert mit Box::new(), wenn du ihn zum Vektor hinzufügst.
Dieses Muster ist für den Aufbau flexibler Systeme unerlässlich, in denen du Sammlungen verwandter, aber unterschiedlicher Objekte verwalten musst, etwa UI-Komponenten, Spieleentitäten oder Plugin-Systeme.
Aufgabe
EinfachErstellen wir ein Benachrichtigungssystem, das verschiedene Arten von Benachrichtigungen verarbeiten kann! Du erstellst eine Sammlung, die verschiedene Benachrichtigungstypen, E-Mails, SMS-Nachrichten und Push-Benachrichtigungen in einem einzigen Vektor mithilfe von Trait-Objekten speichert.
Du organisierst deinen Code auf zwei Dateien verteilt:
notifications.rs: Definiere ein öffentlichesNotify-Trait mit einersend-Methode, die&selfübernimmt und einenStringzurückgibt, der die gesendete Benachrichtigung beschreibt. Erstelle anschließend drei öffentliche Structs:Email: mit einem öffentlichen Feldrecipient(String). Die MethodesendsollEmail to: {recipient}zurückgebenSms: mit einem öffentlichen Feldphone(String). Die MethodesendsollSMS to: {phone}zurückgebenPush: mit einem öffentlichen Felddevice(String). Die MethodesendsollPush to: {device}zurückgeben
main.rs: Binde dein Benachrichtigungsmodul ein und erstelle einen Vektor vom TypVec<Box<dyn Notify>>, der alle drei Benachrichtigungstypen enthält. Verwende die bereitgestellten Eingaben, um jeweils eine Benachrichtigung jedes Typs zu erstellen, füge sie in der Reihenfolge (Email, Sms, Push) zu deinem Vektor hinzu und gib anschließend das Ergebnis des Aufrufs vonsend()für das erste Element des Vektors aus.
Das zentrale Konzept besteht darin, dass Email, Sms und Push trotz ihrer vollständig unterschiedlichen Structs gemeinsam in demselben Vektor enthalten sein können, weil sie das Notify-Trait gemeinsam haben. Jedes Element wird mit Box::new() umschlossen, um das Trait-Objekt zu erstellen.
Deine Ausgabe sollte die erste Benachrichtigung anzeigen:
Email to: {recipient}Beispielsweise mit den Eingaben alice@example.com, 555-1234 und iPhone-12:
Email to: alice@example.comDu erhältst drei Eingaben: den E-Mail-Empfänger, die Telefonnummer und den Gerätenamen.
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 mit den Eingaben
// TODO: Füge sie in der Reihenfolge (Email, Sms, Push) mit Box::new() zum Vektor hinzu
// TODO: Gib das Ergebnis des Aufrufs von send() auf dem ersten Element 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