Die Where-Klausel
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 42 von 61.
Mit zunehmender Anzahl von Trait-Bounds können Funktionssignaturen schwer lesbar werden. Betrachten Sie eine Funktion mit mehreren generischen Parametern, von denen jeder mehrere Traits erfordert:
fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
// ...
}
Das funktioniert, aber die Signatur wird unübersichtlich. Die where-Klausel bietet eine sauberere Alternative, indem sie Trait-Bounds hinter die Parameterliste verschiebt:
fn complex_operation<T, U>(first: T, second: U)
where
T: Display + Clone,
U: Debug + Summary,
{
// ...
}
Beide Versionen sind funktional identisch – die where-Klausel dient rein der Lesbarkeit. Die Anforderungen jedes Typs erscheinen in einer eigenen Zeile, was es einfach macht, zu erfassen, was jeder generische Parameter benötigt.
Die where-Klausel wird besonders wertvoll, wenn Bounds komplex sind oder wenn Sie viele generische Parameter haben. Sie hält den Funktionsnamen und die Parameter auf einen Blick sichtbar, wobei die Constraints separat darunter aufgeführt werden. Dies ist der bevorzugte Stil in den meisten Rust-Codebasen, wenn Bounds über ein einfaches T: Trait hinausgehen.
Aufgabe
EinfachLassen Sie uns ein Datenverarbeitungssystem bauen, das die where-Klausel verwendet, um komplexe Trait-Bounds lesbar zu halten! Sie werden eine generische Funktion mit mehreren Typparametern erstellen, von denen jeder unterschiedliche Traits erfordert, und die Bounds mithilfe der where-Syntax sauber organisieren.
Sie werden Ihren Code über zwei Dateien organisieren:
processor.rs: Definieren Sie zwei öffentliche Traits und eine generische Funktion, die beide verwendet:Summarize— ein Trait mit einersummary-Methode, die&selfentgegennimmt und einenStringzurückgibt.Validate— ein Trait mit einervalidate-Methode, die&selfentgegennimmt und einenboolzurückgibt.
process_items, die zwei Parameter unterschiedlicher generischer Typen akzeptiert. Verwenden Sie einewhere-Klausel, um anzugeben, dass der erste Typ sowohlCloneals auchSummarizeimplementieren muss, während der zweite TypValidateimplementieren muss. Die Funktion sollte die Zusammenfassung des ersten Elements ausgeben und dann ausgeben, ob das zweite Element gültig ist.main.rs: Erstellen Sie zwei öffentliche Structs, welche die erforderlichen Traits implementieren:Article— mit einem öffentlichentitle-Feld (String). Implementieren SieSummarize, um"Article: {}"mit dem Titel zurückzugeben, und leiten SieCloneab (derive).Form— mit einem öffentlichenfilled-Feld (bool). Implementieren SieValidate, um den Wert vonfilledzurückzugeben.
process_itemsmit beiden auf.
Die where-Klausel macht Ihre Funktionssignatur viel sauberer, als alle Bounds in die spitzen Klammern zu quetschen. Die Anforderungen jedes Typs erscheinen in einer eigenen Zeile, was es einfach macht zu sehen, was jeder generische Parameter benötigt.
Ihre Ausgabe sollte die Zusammenfassung gefolgt vom Validierungsergebnis zeigen:
Article: {title}
Valid: {true/false}Zum Beispiel mit den Eingaben Rust Tips und true:
Article: Rust Tips
Valid: trueUnd mit den Eingaben Breaking News und false:
Article: Breaking News
Valid: falseSie erhalten zwei Eingaben: den Artikeltitel und ob das Formular ausgefüllt ist (als bool parsen).
Spickzettel
Die where-Klausel bietet eine übersichtlichere Möglichkeit, Trait-Bounds für generische Funktionen anzugeben, indem die Einschränkungen hinter die Parameterliste verschoben werden:
fn complex_operation<T, U>(first: T, second: U)
where
T: Display + Clone,
U: Debug + Summary,
{
// ...
}
Dies ist funktional identisch mit Inline-Trait-Bounds, verbessert jedoch die Lesbarkeit:
fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
// ...
}
Die where-Klausel ist besonders nützlich, wenn:
- Sie mehrere generische Parameter haben
- Jeder Parameter mehrere Trait-Bounds erfordert
- Sie die Funktionssignatur sauber und übersichtlich halten möchten
Die Anforderungen jedes Typs erscheinen in einer eigenen Zeile, was es einfach macht zu sehen, was jeder generische Parameter benötigt. Dies ist der bevorzugte Stil in den meisten Rust-Codebasen, wenn die Bounds über ein einfaches T: Trait hinausgehen.
Probier es selbst
mod processor;
use processor::{Summarize, Validate, process_items};
use std::io;
// TODO: Definiere ein öffentliches Struct Article mit einem öffentlichen title-Feld (String)
// Leite Clone für Article ab
// TODO: Implementiere Summarize für Article
// Die summary-Methode sollte "Article: {title}" zurückgeben
// TODO: Definiere ein öffentliches Struct Form mit einem öffentlichen filled-Feld (bool)
// TODO: Implementiere Validate für Form
// Die validate-Methode sollte den Wert von filled zurückgeben
fn main() {
let mut title = String::new();
io::stdin().read_line(&mut title).expect("Failed to read line");
let title = title.trim().to_string();
let mut filled_input = String::new();
io::stdin().read_line(&mut filled_input).expect("Failed to read line");
let filled: bool = filled_input.trim().parse().expect("Failed to parse bool");
// TODO: Erstelle ein Article mit dem angegebenen Titel
// TODO: Erstelle ein Form mit dem angegebenen filled-Wert
// TODO: Rufe process_items mit dem Article und dem Form 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