Syntax für Trait Bounds
Teil des Abschnitts Objektorientierte Programmierung der Rust-Journey von Coddy. Lektion 40 von 61.
Bisher hast du gelernt, Traits zu definieren und sie für Structs zu implementieren. Außerdem hast du mit Generics gearbeitet, um flexiblen Code zu schreiben. Jetzt ist es an der Zeit, diese Konzepte zu kombinieren: Traits zu verwenden, um einzuschränken, welche Typen eine generische Funktion akzeptieren kann.
Betrachte eine generische Funktion, die eine bestimmte Methode für ihren Parameter aufrufen muss. Ohne Einschränkungen kann Rust nicht garantieren, dass der Typ T über diese Methode verfügt:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Fehler! T hat möglicherweise keine summarize()-Methode
}
Die Lösung ist eine Trait-Einschränkung. Indem du : TraitName nach dem generischen Parameter hinzufügst, teilst du Rust mit, dass T diesen Trait implementieren muss:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Nun weiß Rust, dass diese Methode existiert
}
Die Syntax <T: Summary> liest sich als „T ist any type, das Summary implements.“ Jetzt akzeptiert die function nur Typen, die das erforderliche Verhalten have. Wenn du versuchst, einen Typ zu übergeben, der Summary nicht implements, wird der Compiler ihn mit einem eindeutigen Fehler ablehnen.
Dieses Muster ist leistungsstark, weil es dir ermöglicht, generischen Code zu schreiben, der dennoch Zugriff auf spezifische Funktionalität hat. Deine Funktion bleibt flexibel und funktioniert mit jedem Typ, aber nur mit Typen, die das von dir benötigte Verhalten bereitstellen.
Aufgabe
EinfachLass uns ein Benachrichtigungssystem erstellen, das Trait-Bounds verwendet, um sicherzustellen, dass nur korrekt formatierte Nachrichten gesendet werden können! Du erstellst eine generische Funktion, die jeden Typ akzeptiert, der ein bestimmtes Trait implementiert, und demonstrierst damit, wie Trait-Bounds generische Parameter einschränken.
Du wirst deinen Code auf zwei Dateien verteilen:
notification.rs: Definiere ein öffentlichesNotify-Trait mit einer Methode namensmessage, die&selfübernimmt und einenStringzurückgibt. Erstelle anschließend zwei öffentliche Structs, die dieses Trait implementieren:Email: mit einem öffentlichen Feldsubject(String). Die MethodemessagesollEmail: {subject}zurückgeben.SMS: mit einem öffentlichen Feldcontent(String). Die MethodemessagesollSMS: {content}zurückgeben.
send_notification, die jeden TypTakzeptiert, der dasNotify-Trait implementiert. Diese Funktion soll das Ergebnis des Aufrufs vonmessage()für das Element ausgeben.main.rs: Binde dein Benachrichtigungsmodul ein und verwende die bereitgestellten Eingaben, um sowohl eineEmail- als auch eineSMS-Instanz zu erstellen. Rufesend_notificationmit jeder Instanz auf, um zu zeigen, dass deine generische Funktion mit jedem Typ funktioniert, derNotifyimplementiert.
Die wichtigste Erkenntnis hierbei ist, dass send_notification den konkreten Typ, den es erhält, nicht kennt. Es weiß nur, dass der Typ eine Nachricht erzeugen kann. Der Trait-Bound T: Notify garantiert diese Fähigkeit.
Deine Ausgabe sollte zeigen, dass beide Benachrichtigungen gesendet werden:
Email: {subject}
SMS: {content}Zum Beispiel mit den Eingaben Meeting Tomorrow und On my way!:
Email: Meeting Tomorrow
SMS: On my way!Du erhältst zwei Eingaben: den E-Mail-Betreff und den SMS-Inhalt.
Probier es selbst
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Eingaben lesen
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Erstelle eine Email-Instanz mit dem subject
// TODO: Erstelle eine SMS-Instanz mit dem content
// TODO: Rufe send_notification mit der email auf
// TODO: Rufe send_notification mit der sms auf
}
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 PlayerÜbe selbstständig: Online-Rust-Compiler