Itérer sur des objets de trait
Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 47 sur 61.
Maintenant que vous pouvez stocker différents types dans un Vec<Box<dyn Trait>>, l'étape suivante consiste à traiter cette collection. L'itération sur des objets de trait fonctionne exactement comme l'itération sur n'importe quel vecteur : vous parcourez chaque élément et appelez les méthodes définies par le trait.
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}
impl Shape for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 2.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
Box::new(Circle { radius: 1.0 }),
];
for shape in &shapes {
println!("Area: {}", shape.area());
}
}
Lorsque vous itérez avec for shape in &shapes, chaque shape est une référence vers un Box<dyn Shape>. Vous pouvez appeler n'importe quelle méthode du trait Shape directement — Rust gère automatiquement le dispatch dynamique, en appelant l'implémentation correcte pour chaque type concret.
Ce modèle est puissant pour agréger des résultats. Vous pourriez additionner toutes les surfaces, filtrer les formes par taille ou transformer la collection. L'idée maîtresse est qu'une fois que les éléments se trouvent dans une collection d'objets de trait, vous interagissez avec eux exclusivement via l'interface du trait, quels que soient leurs types sous-jacents.
Défi
FacileConstruisons un calculateur de prix pour un panier d'achat pouvant contenir différents types d'articles ! Vous allez créer un système où les produits et les services — chacun ayant une logique de tarification différente — peuvent être stockés ensemble et traités pour calculer un prix total.
Vous organiserez votre code sur deux fichiers :
items.rs: Définissez un trait publicPriceableavec une méthodepricequi prend&selfet retourne unf64. Ensuite, créez deux structs publiques :Product— avec des champs publicsname(String) etcost(f64). Sa méthodepricedoit simplement retourner lecost.Service— avec des champs publicsname(String),hourly_rate(f64), ethours(f64). Sa méthodepricedoit retournerhourly_rate * hours.
main.rs: Importez votre module items et créez un vecteur de typeVec<Box<dyn Priceable>>. Utilisez les entrées pour créer un Product et un Service, ajoutez-les à votre vecteur, puis parcourez la collection pour calculer et afficher le prix total de tous les articles.
La puissance des objets de trait (trait objects) s'illustre ici — votre code d'itération n'a pas besoin de savoir s'il traite un Product ou un Service. Il appelle simplement price() sur chaque élément, et Rust délègue automatiquement à la bonne implémentation.
Votre sortie doit afficher le prix total avec une décimale :
Total: ${total}Par exemple, avec les entrées Laptop, 999.99, Consulting, 150.0, et 3.0 :
Total: $1449.99Et avec les entrées Book, 29.99, Tutoring, 50.0, et 2.0 :
Total: $129.99Vous recevrez cinq entrées : le nom du produit, le coût du produit, le nom du service, le taux horaire et les heures travaillées (analysez les valeurs numériques en tant que f64).
Aide-mémoire
Pour itérer sur une collection d'objets de trait, utilisez une boucle for standard avec une référence au vecteur :
for item in &collection {
item.trait_method();
}
Lors de l'itération avec for item in &vec, chaque item est une référence à un Box<dyn Trait>. Vous pouvez appeler n'importe quelle méthode du trait directement — Rust gère automatiquement la distribution dynamique (dynamic dispatch), en appelant l'implémentation correcte pour chaque type concret.
Exemple d'itération sur des objets de trait pour agréger des résultats :
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}
impl Shape for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 2.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
];
for shape in &shapes {
println!("Area: {}", shape.area());
}
}
Ce motif est utile pour agréger des résultats — vous pouvez additionner des valeurs, filtrer selon des critères ou transformer la collection tout en interagissant avec les éléments uniquement via l'interface du trait.
Essayez vous-même
mod items;
use items::{Priceable, Product, Service};
fn main() {
// Lire les entrées
let mut product_name = String::new();
std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
let product_name = product_name.trim().to_string();
let mut product_cost = String::new();
std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");
let mut service_name = String::new();
std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
let service_name = service_name.trim().to_string();
let mut hourly_rate = String::new();
std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");
let mut hours = String::new();
std::io::stdin().read_line(&mut hours).expect("Failed to read line");
let hours: f64 = hours.trim().parse().expect("Failed to parse");
// TODO: Créer un Product en utilisant product_name et product_cost
// TODO: Créer un Service en utilisant service_name, hourly_rate, et hours
// TODO: Créer un Vec<Box<dyn Priceable>> et y ajouter les deux éléments
// TODO: Parcourir le vecteur et calculer le prix total
// TODO: Afficher le total avec une décimale
// Format: Total: ${total:.1}
}
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édia9Objets de trait
Dispatch dynamiqueVecteurs de traitsItérer sur des objets de traitRécapitulatif - Gestionnaire de zoo