Syntaxe des contraintes de trait
Fait partie de la section Object Oriented Programming du Journey Rust de Coddy. Leçon 40 sur 61.
Jusqu’à présent, tu as appris à définir des traits et à les implémenter pour des structures. Tu as également travaillé avec les génériques pour écrire du code flexible. Il est maintenant temps de combiner ces concepts : utiliser des traits pour restreindre les types qu’une fonction générique peut accept.
Considérons une generic function qui doit call une méthode spécifique sur son paramètre. Sans aucune contrainte, Rust ne peut pas garantir que le type T possède cette méthode :
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Erreur ! T pourrait ne pas avoir summarize()
}
La solution est une contrainte de trait. En ajoutant : TraitName après le paramètre générique, vous indiquez à Rust que T doit implémenter ce trait :
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Maintenant Rust sait que cette méthode existe
}
La syntaxe <T: Summary> se lit comme suit : « T est any type qui implements Summary. » Désormais, la function n’accepte que les types qui have le comportement requis. Si vous essayez de transmettre un type qui n’implémente pas Summary, le compilateur le rejettera avec une erreur claire.
Ce pattern est puissant, car il vous permet d’écrire du code generic qui a toujours accès à des fonctionnalités spécifiques. Votre function reste flexible, elle fonctionne avec any type, mais uniquement avec les types qui fournissent le comportement dont vous avez besoin.
Défi
FacileConstruisons un système de notification qui utilise des contraintes de traits afin de garantir que seuls les messages correctement formatés puissent être envoyés ! Vous allez créer une fonction générique qui accepte tout type implémentant un trait spécifique, afin de montrer comment les contraintes de traits limitent les paramètres génériques.
Vous allez organiser votre code dans deux fichiers :
notification.rs: définissez un traitNotifypublic avec une méthode appeléemessagequi prend&selfet returns uneString. Créez ensuite deux structures publiques qui implémentent ce trait :Email: avec un champsubjectpublic (String). Sa méthodemessagedoit renvoyerEmail: {subject}SMS: avec un champcontentpublic (String). Sa méthodemessagedoit renvoyerSMS: {content}
send_notificationqui accepte tout typeTimplémentant le traitNotify. Cette fonction doit afficher le résultat de l'appel demessage()sur l'item.main.rs: ajoutez votre module de notification et utilisez les entrées fournies pour créer une instance deEmailet une instance deSMS. Appelezsend_notificationavec chacune d'elles afin de montrer que votre fonction générique fonctionne avec tout type qui implémenteNotify.
L'idée essentielle ici est que send_notification ne connaît pas le type concret qu'il reçoit. Il sait seulement que le type peut produire un message. La contrainte de trait T: Notify garantit cette capacité.
Votre sortie doit montrer que les deux notifications sont envoyées :
Email: {subject}
SMS: {content}Par exemple, avec les entrées Meeting Tomorrow et On my way! :
Email: Meeting Tomorrow
SMS: On my way!Vous recevrez deux entrées : le sujet de l'e-mail et le contenu du SMS.
Essayez vous-même
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Lire les entrées
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Créer une instance Email avec le subject
// TODO: Créer une instance SMS avec le content
// TODO: Appeler send_notification avec l'email
// TODO: Appeler send_notification avec le sms
}
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édiaEntraînez-vous par vous-même : Compilateur Rust en ligne