Menu
Coddy logo textTech

Vecteurs de traits

Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 46 sur 61.

Vous avez vu comment Box<dyn Trait> permet à une seule variable de contenir différents types. La véritable puissance apparaît lorsque vous combinez cela avec des collections — stocker plusieurs types différents dans un seul vecteur.

Un vecteur classique nécessite que tous les éléments soient du même type. Vous ne pouvez pas avoir un Vec<Dog> qui contient également des chats. Mais avec les objets de trait, vous pouvez créer un vecteur qui contient n'importe quoi implémentant un trait partagé :

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;
struct Bird;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

impl Speak for Bird {
    fn speak(&self) -> String { String::from("Tweet!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Bird),
    ];
}

Le type Vec<Box<dyn Speak>> signifie « un vecteur d'objets de trait mis en boîte (boxed) ». Chaque élément est un Box pointant vers quelque chose qui implémente Speak — les types réels peuvent différer. Vous enveloppez chaque valeur avec Box::new() lors de son ajout au vecteur.

Ce patron est essentiel pour construire des systèmes flexibles où vous devez gérer des collections d'objets liés mais différents — comme des composants d'interface utilisateur, des entités de jeu ou des systèmes de plugins.

challenge icon

Défi

Facile

Construisons un système de notification capable de gérer différents types d'alertes ! Vous allez créer une collection qui stocke divers types de notifications — e-mails, messages SMS et notifications push — le tout dans un seul vecteur en utilisant des objets de trait (trait objects).

Vous organiserez votre code sur deux fichiers :

  • notifications.rs : Définissez un trait public Notify avec une méthode send qui prend &self et renvoie un String décrivant la notification envoyée. Créez ensuite trois structures publiques :
    • Email — avec un champ public recipient (String). Sa méthode send doit renvoyer Email to: {recipient}
    • Sms — avec un champ public phone (String). Sa méthode send doit renvoyer SMS to: {phone}
    • Push — avec un champ public device (String). Sa méthode send doit renvoyer Push to: {device}
  • main.rs : Importez votre module notifications et créez un vecteur de type Vec<Box<dyn Notify>> qui contient les trois types de notifications. Utilisez les entrées fournies pour créer un exemplaire de chaque type de notification, ajoutez-les à votre vecteur dans l'ordre (Email, Sms, Push), puis affichez le résultat de l'appel à send() sur le premier élément du vecteur.

Le concept clé ici est que, bien que Email, Sms et Push soient des structures complètement différentes, elles peuvent toutes coexister dans le même vecteur car elles partagent le trait Notify. Chaque élément est enveloppé avec Box::new() pour créer l'objet de trait.

Votre sortie devrait afficher la première notification :

Email to: {recipient}

Par exemple, avec les entrées alice@example.com, 555-1234 et iPhone-12 :

Email to: alice@example.com

Vous recevrez trois entrées : le destinataire de l'e-mail, le numéro de téléphone et le nom de l'appareil.

Aide-mémoire

Un Vec<Box<dyn Trait>> permet de stocker plusieurs types différents dans un seul vecteur, tant qu'ils implémentent tous le même trait.

Les vecteurs réguliers exigent que tous les éléments soient du même type, mais les objets de trait permettent des collections hétérogènes :

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
    ];
}

Chaque élément est enveloppé avec Box::new() lors de l'ajout au vecteur. Les types réels peuvent différer tant qu'ils implémentent le trait partagé.

Essayez vous-même

mod notifications;

use notifications::{Notify, Email, Sms, Push};

fn main() {
    // Lire les entrées
    let mut recipient = String::new();
    std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
    let recipient = recipient.trim().to_string();

    let mut phone = String::new();
    std::io::stdin().read_line(&mut phone).expect("Failed to read line");
    let phone = phone.trim().to_string();

    let mut device = String::new();
    std::io::stdin().read_line(&mut device).expect("Failed to read line");
    let device = device.trim().to_string();

    // TODO: Créer un Vec<Box<dyn Notify>> pour contenir différents types de notifications
    // TODO: Créer des instances de Email, Sms, et Push en utilisant les entrées
    // TODO: Les ajouter au vecteur dans l'ordre (Email, Sms, Push) en utilisant Box::new()
    // TODO: Afficher le résultat de l'appel à send() sur le premier élément
}
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