Syntax für Trait Bounds
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 40 von 61.
Bisher haben Sie gelernt, Traits zu definieren und sie für Structs zu implementieren. Sie haben auch mit Generics gearbeitet, um flexiblen Code zu schreiben. Jetzt ist es an der Zeit, diese Konzepte zu kombinieren – indem Traits verwendet werden, um einzuschränken, welche Typen eine generische Funktion akzeptieren kann.
Betrachten Sie eine generische Funktion, die eine bestimmte Methode auf ihrem Parameter aufrufen muss. Ohne Einschränkungen (Constraints) hat Rust keine Garantie, dass der Typ T über diese Methode verfügt:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Fehler! T verfügt möglicherweise nicht über summarize()
}
Die Lösung ist ein Trait Bound. 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()); // Jetzt weiß Rust, dass diese Methode existiert
}
Die Syntax <T: Summary> liest sich als „T ist ein beliebiger Typ, der Summary implementiert“. Jetzt akzeptiert die Funktion nur noch Typen, die das erforderliche Verhalten aufweisen. Wenn Sie versuchen, einen Typ zu übergeben, der Summary nicht implementiert, wird der Compiler dies mit einer eindeutigen Fehlermeldung ablehnen.
Dieses Muster ist leistungsstark, da es Ihnen ermöglicht, generischen Code zu schreiben, der dennoch Zugriff auf spezifische Funktionalität hat. Ihre Funktion bleibt flexibel – sie funktioniert mit jedem Typ –, aber nur mit Typen, die das von Ihnen benötigte Verhalten bereitstellen.
Aufgabe
EinfachLassen Sie uns ein Benachrichtigungssystem bauen, das Trait-Bounds verwendet, um sicherzustellen, dass nur ordnungsgemäß formatierte Nachrichten gesendet werden können! Sie werden eine generische Funktion erstellen, die jeden Typ akzeptiert, der ein bestimmtes Trait implementiert, und so demonstrieren, wie Trait-Bounds generische Parameter einschränken.
Sie werden Ihren Code in zwei Dateien organisieren:
notification.rs: Definieren Sie ein öffentlichesNotify-Trait mit einer Methode namensmessage, die&selfnimmt und einenStringzurückgibt. Erstellen Sie dann zwei öffentliche Structs, die dieses Trait implementieren:Email— mit einem öffentlichensubject-Feld (String). Diemessage-Methode sollte"Email: {subject}"zurückgeben.SMS— mit einem öffentlichencontent-Feld (String). Diemessage-Methode sollte"SMS: {content}"zurückgeben.
send_notification, die jeden TypTakzeptiert, der dasNotify-Trait implementiert. Diese Funktion sollte das Ergebnis des Aufrufs vonmessage()auf dem Element ausgeben.main.rs: Binden Sie Ihr Benachrichtigungsmodul ein und verwenden Sie die bereitgestellten Eingaben, um sowohl eineEmail- als auch eineSMS-Instanz zu erstellen. Rufen Siesend_notificationmit jeder Instanz auf, um zu demonstrieren, dass Ihre generische Funktion mit jedem Typ funktioniert, derNotifyimplementiert.
Die entscheidende Erkenntnis hierbei ist, dass send_notification den konkreten Typ, den sie erhält, nicht kennt – sie weiß nur, dass der Typ eine Nachricht erzeugen kann. Der Trait-Bound T: Notify garantiert diese Fähigkeit.
Ihre 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!Sie erhalten zwei Eingaben: den E-Mail-Betreff und den SMS-Inhalt.
Spickzettel
Ein Trait-Bound schränkt einen generischen Typparameter so ein, dass er nur Typen akzeptiert, die ein bestimmtes Trait implementieren.
Die Syntax für Trait-Bounds verwendet : TraitName nach dem generischen Parameter:
fn function_name<T: TraitName>(item: T) {
// Kann nun Methoden von TraitName auf item aufrufen
}
Beispiel mit einem Trait-Bound:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize());
}
Die Notation <T: Summary> bedeutet „T ist ein beliebiger Typ, der Summary implementiert“. Dies ermöglicht es der Funktion, Trait-Methoden aufzurufen, während sie generisch bleibt.
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 Betreff
// TODO: Erstelle eine SMS-Instanz mit dem Inhalt
// 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 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 Player