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.
Défi
FacileConstruisons 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éthodesummaryqui prend&selfet retourne unStringValidate— un trait avec une méthodevalidatequi prend&selfet retourne unbool
process_itemsqui accepte deux paramètres de types génériques différents. Utilisez une clausewherepour spécifier que le premier type doit implémenter à la foisCloneetSummarize, tandis que le second type doit implémenterValidate. 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 publictitle(String). ImplémentezSummarizepour retourner"Article: {}"avec le titre, et dérivezClone.Form— avec un champ publicfilled(bool). ImplémentezValidatepour retourner la valeur defilled.
process_itemsavec 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: trueEt avec les entrées Breaking News et false :
Article: Breaking News
Valid: falseVous 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
}
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
1Méthodes et comportement
Introduction aux blocs d'implémentationLe paramètre SelfMéthodes mutablesFonctions associéesBlocs d'implémentation multiplesChaînage de méthodesRécapitulatif - Actions sur le rectangle4Projet : Animal virtuel
Définir l'animalNourrir l'animal7Traits standards
Le trait DebugLe trait DisplayClone et CopyLes traits d'égalitéRécapitulatif - Point affichable10Projet : Système de documents
Le trait DrawComposant texte2Encapsulation et modules
Bases des modulesLe mot-clé publicChamps privésGettersSettersRécapitulatif - Casier sécurisé5Généricité
Structures génériquesMéthodes génériquesPlusieurs types génériquesFonctions génériquesRécapitulatif - Point de coordonnées8Les Traits comme contraintes
Syntaxe des contraintes de traitContraintes multiplesLa clause whereRetourner des types avec des traitsRécapitulatif - Afficheur générique11Design Patterns en Rust
Le pattern NewtypeCompositionLe trait DropFrom et IntoRécapitulatif - Mock de Smart Pointer3Enums avancées
Enums avec donnéesMéthodes sur les EnumsMatching des variantes de donnéesL'Enum Option revisitéeRécapitulatif - Enum Shape6Définition des Traits
Qu'est-ce qu'un Trait ?Implémenter des TraitsImplémentations par défautRedéfinir les comportements par défautTraits avec paramètresRécapitulatif - Lecteur multimédia