Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 einer summary-Methode, die &self entgegennimmt und einen String zurückgibt.
    • Validate — ein Trait mit einer validate-Methode, die &self entgegennimmt und einen bool zurückgibt.
    Erstellen Sie dann eine öffentliche generische Funktion namens process_items, die zwei Parameter unterschiedlicher generischer Typen akzeptiert. Verwenden Sie eine where-Klausel, um anzugeben, dass der erste Typ sowohl Clone als auch Summarize implementieren muss, während der zweite Typ Validate implementieren 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 öffentlichen title-Feld (String). Implementieren Sie Summarize, um "Article: {}" mit dem Titel zurückzugeben, und leiten Sie Clone ab (derive).
    • Form — mit einem öffentlichen filled-Feld (bool). Implementieren Sie Validate, um den Wert von filled zurückzugeben.
    Verwenden Sie die bereitgestellten Eingaben, um ein Article und ein Form zu erstellen, und rufen Sie dann process_items mit 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: true

Und mit den Eingaben Breaking News und false:

Article: Breaking News
Valid: false

Sie 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
}
quiz iconTeste dich selbst

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