Menu
Coddy logo textTech

La clause where

Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 42 sur 61.

À mesure que vous ajoutez des bornes de trait, les signatures de fonction peuvent devenir difficiles à lire. Considérez une fonction avec plusieurs paramètres génériques, chacun nécessitant plusieurs traits :

fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
    // ...
}

Cela fonctionne, mais la signature devient encombrée. La clause where offre une alternative plus propre en déplaçant les liaisons de trait après la liste des paramètres :

fn complex_operation<T, U>(first: T, second: U)
where
    T: Display + Clone,
    U: Debug + Summary,
{
    // ...
}

Les deux versions sont fonctionnellement identiques — la clause where est purement une question de lisibilité. Les exigences de chaque type apparaissent sur leur propre ligne, ce qui permet de voir facilement ce dont chaque paramètre générique a besoin.

La clause where devient particulièrement précieuse lorsque les bornes sont complexes ou lorsque vous avez de nombreux paramètres génériques. Elle permet de garder le nom de la fonction et les paramètres visibles d'un coup d'œil, avec les contraintes listées séparément ci-dessous. C'est le style privilégié dans la plupart des bases de code Rust lorsque les bornes vont au-delà d'un simple T: Trait.

challenge icon

Défi

Facile

Construisons un système de traitement de données qui utilise la clause where pour garder les limites de traits complexes lisibles ! Vous allez créer une fonction générique avec plusieurs paramètres de type, chacun nécessitant des traits différents, et organiser les limites proprement en utilisant la syntaxe where.

Vous organiserez votre code sur deux fichiers :

  • processor.rs : Définissez deux traits publics et une fonction générique qui les utilise tous les deux :
    • Summarize — un trait avec une méthode summary qui prend &self et retourne un String
    • Validate — un trait avec une méthode validate qui prend &self et retourne un bool
    Ensuite, créez une fonction générique publique appelée process_items qui accepte deux paramètres de types génériques différents. Utilisez une clause where pour spécifier que le premier type doit implémenter à la fois Clone et Summarize, tandis que le second type doit implémenter Validate. La fonction doit afficher le résumé du premier élément, puis afficher si le second élément est valide.
  • main.rs : Créez deux structures publiques qui implémentent les traits requis :
    • Article — avec un champ public title (String). Implémentez Summarize pour retourner "Article: {}" avec le titre, et dérivez Clone.
    • Form — avec un champ public filled (bool). Implémentez Validate pour retourner la valeur de filled.
    Utilisez les entrées fournies pour créer un Article et un Form, puis appelez process_items avec les deux.

La clause where rend la signature de votre fonction beaucoup plus propre que de tasser toutes les limites dans les chevrons. Les exigences de chaque type apparaissent sur leur propre ligne, ce qui permet de voir facilement ce dont chaque paramètre générique a besoin.

Votre sortie doit afficher le résumé suivi du résultat de la validation :

Article: {title}
Valid: {true/false}

Par exemple, avec les entrées Rust Tips et true :

Article: Rust Tips
Valid: true

Et avec les entrées Breaking News et false :

Article: Breaking News
Valid: false

Vous recevrez deux entrées : le titre de l'article et si le formulaire est rempli (à analyser comme un bool).

Aide-mémoire

La clause where offre une manière plus propre de spécifier les limites de trait pour les fonctions génériques en déplaçant les contraintes après la liste des paramètres :

fn complex_operation<T, U>(first: T, second: U)
where
    T: Display + Clone,
    U: Debug + Summary,
{
    // ...
}

C'est fonctionnellement identique aux limites de trait en ligne, mais cela améliore la lisibilité :

fn complex_operation<T: Display + Clone, U: Debug + Summary>(first: T, second: U) {
    // ...
}

La clause where est particulièrement utile lorsque :

  • Vous avez plusieurs paramètres génériques
  • Chaque paramètre nécessite plusieurs limites de trait
  • Vous voulez garder la signature de la fonction propre et facile à parcourir

Les exigences de chaque type apparaissent sur leur propre ligne, ce qui permet de voir facilement ce dont chaque paramètre générique a besoin. C'est le style préféré dans la plupart des bases de code Rust lorsque les limites s'étendent au-delà d'un simple T: Trait.

Essayez vous-même

mod processor;

use processor::{Summarize, Validate, process_items};
use std::io;

// TODO: Définir une struct publique Article avec un champ public title (String)
// Dériver Clone pour Article

// TODO: Implémenter Summarize pour Article
// La méthode summary doit retourner "Article: {title}"

// TODO: Définir une struct publique Form avec un champ public filled (bool)

// TODO: Implémenter Validate pour Form
// La méthode validate doit retourner la valeur de filled

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: Créer un Article avec le titre donné
    
    // TODO: Créer un Form avec la valeur filled donnée
    
    // TODO: Appeler process_items avec l'article et le formulaire
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming