Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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 public Priceable avec une méthode price qui prend &self et retourne un f64. Ensuite, créez deux structs publiques :
    • Product — avec des champs publics name (String) et cost (f64). Sa méthode price doit simplement retourner le cost.
    • Service — avec des champs publics name (String), hourly_rate (f64), et hours (f64). Sa méthode price doit retourner hourly_rate * hours.
  • main.rs : Importez votre module items et créez un vecteur de type Vec<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.99

Et avec les entrées Book, 29.99, Tutoring, 50.0, et 2.0 :

Total: $129.99

Vous 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}
}
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