Contraintes multiples
Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 41 sur 61.
Parfois, une seule borne de trait ne suffit pas. Vous pourriez avoir besoin d'un type générique qui peut à la fois être affiché et fournir un résumé. Rust vous permet d'exiger plusieurs traits en utilisant la syntaxe +.
Voici comment spécifier qu'un type doit implémenter deux traits :
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
La contrainte T: Display + Summary signifie que « T doit implémenter à la fois Display et Summary ». À l'intérieur de la fonction, vous pouvez utiliser les fonctionnalités des deux traits — l'affichage avec {} (provenant de Display) et l'appel à summarize() (provenant de Summary).
Vous pouvez enchaîner autant de traits que nécessaire :
fn process<T: Display + Summary + Clone>(item: T) {
// Peut afficher, résumer ET cloner
}
Ce pattern est essentiel lorsque votre fonction repose sur plusieurs comportements. Plutôt que d'accepter n'importe quel type en espérant que cela fonctionne, vous déclarez explicitement les capacités exactes requises — et le compilateur l'impose au moment de la compilation.
Défi
FacileConstruisons un système d'inspection de produits qui exige que les articles possèdent plusieurs capacités ! Vous allez créer une fonction générique qui n'accepte que les types implémentant à la fois un trait personnalisé et un trait standard, démontrant ainsi comment la syntaxe + combine plusieurs bornes (bounds).
Vous organiserez votre code sur deux fichiers :
product.rs: Définissez un trait publicInspectableavec une méthode appeléeinspectqui prend&selfet retourne unStringcontenant les détails de l'inspection. Créez ensuite une structure publiqueGadgetavec des champs publicsname(String) etserial(u32). Votre Gadget doit implémenter à la foisInspectable(retournant"Inspecting: {}"avec le nom) etstd::fmt::Display(formaté comme"{} (SN: {})"). Enfin, créez une fonction générique publique appeléefull_reportqui accepte n'importe quel typeTimplémentant à la foisDisplayetInspectable. Cette fonction doit imprimer deux lignes : d'abord l'article en utilisant le formateur{}, puis le résultat de l'appel àinspect().main.rs: Importez votre module product et créez une instance deGadgeten utilisant les entrées fournies. Appelezfull_reportavec votre gadget pour montrer qu'il satisfait aux exigences des deux traits.
La puissance des bornes multiples est que votre fonction full_report peut utiliser les capacités des deux traits — afficher l'article proprement ET obtenir les détails de l'inspection — le tout garanti au moment de la compilation.
Votre sortie devrait afficher à la fois le format d'affichage et le résultat de l'inspection :
{name} (SN: {serial})
Inspecting: {name}Par exemple, avec les entrées Smartwatch et 98765 :
Smartwatch (SN: 98765)
Inspecting: SmartwatchVous recevrez deux entrées : le nom du gadget et le numéro de série (à analyser comme un u32).
Aide-mémoire
Vous pouvez exiger plusieurs limites de trait (trait bounds) sur un type générique en utilisant la syntaxe + :
fn function_name<T: Trait1 + Trait2>(item: T) {
// Peut utiliser les capacités des deux traits
}
Exemple avec Display et un trait personnalisé :
use std::fmt::Display;
trait Summary {
fn summarize(&self) -> String;
}
fn announce<T: Display + Summary>(item: T) {
println!("Breaking news: {}", item);
println!("Summary: {}", item.summarize());
}
Vous pouvez enchaîner plusieurs traits selon vos besoins :
fn process<T: Display + Summary + Clone>(item: T) {
// Peut afficher, résumer ET cloner
}
Le compilateur impose que le type implémente tous les traits spécifiés au moment de la compilation.
Essayez vous-même
mod product;
use product::{Gadget, full_report};
fn main() {
// Lire les entrées
let mut name = String::new();
std::io::stdin().read_line(&mut name).expect("Failed to read line");
let name = name.trim().to_string();
let mut serial_input = String::new();
std::io::stdin().read_line(&mut serial_input).expect("Failed to read line");
let serial: u32 = serial_input.trim().parse().expect("Failed to parse serial");
// TODO: Créer une instance de Gadget avec le nom et le numéro de série
// TODO: Appeler full_report avec votre gadget
}
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