Exprimer des alternatives avec les énumérations et match

RustBeginner
Pratiquer maintenant

Introduction

Une structure regroupe des champs qui vont toujours ensemble. Une énumération représente une valeur qui peut prendre l’une de plusieurs formes, chaque forme pouvant contenir des données différentes. L’expression match de Rust permet ensuite de traiter explicitement chacune de ces alternatives.

Vous allez terminer une énumération de messages, déstructurer les coordonnées d’une variante et étendre le modèle avec une nouvelle variante. Le compilateur vous montrera volontairement pourquoi toutes les variantes possibles doivent être couvertes avant que le processeur de messages puisse s’exécuter.

Définir des variantes porteuses de données

Dans cette étape, vous allez définir trois formes possibles pour un même type Message.

Accédez au projet préparé et ouvrez le fichier source :

cd /home/labex/project/message-processor
nano src/main.rs

Une déclaration d’énumération liste ses variantes entre accolades. Pour construire une variante, utilisez le nom de l’énumération, ::, puis le nom de la variante. Les différentes variantes d’une même énumération peuvent contenir des types de données différents.

Remplacez le commentaire de l’étape 1 par :

    Text(String),
    Move { x: i32, y: i32 },
    Stop,

Text(String) contient une valeur sans nom, une forme que Rust appelle une variante de type tuple. Move { x, y } contient des champs nommés : il s’agit donc d’une variante de type structure. Stop ne contient aucune donnée supplémentaire ; c’est une variante unitaire. Retenez surtout les trois formes suivantes : « une valeur sans nom, des champs nommés ou aucune donnée ». Il n’est pas nécessaire de mémoriser immédiatement les noms formels. Malgré leurs formes différentes, toutes les valeurs construites ont le même type Message.

Enregistrez avec Ctrl+O, appuyez sur Entrée, puis quittez avec Ctrl+X. Vérifiez et exécutez le processeur préparé :

cargo check
cargo run --quiet

La sortie doit être :

Text: Maintenance at 9
Move received
Stop requested

Le match préparé sélectionne une branche pour chaque variante. Chaque branche suit la forme pattern => behavior, et des virgules séparent les branches courtes. Le motif Text(text) donne un nom à la chaîne contenue. La branche Move donne également des noms à x et y, puis les regroupe temporairement sous la forme (x, y). Ici, les parenthèses créent un tuple à deux valeurs utilisé uniquement comme structure intermédiaire déjà préparée ; les tuples ne sont pas un objectif d’apprentissage de ce laboratoire. Le nom _coordinates commence par _, ce qui indique à Rust que le fait de ne pas utiliser cette valeur temporaire est intentionnel. Le premier lancement ne produit ainsi aucun avertissement. À l’étape suivante, vous remplacerez cette structure intermédiaire et utiliserez les deux coordonnées dans la sortie affichée.

Utiliser les données déstructurées d’une variante

Dans cette étape, vous allez modifier la branche Move du match afin que les coordonnées extraites par son motif apparaissent dans la sortie.

Les motifs peuvent décomposer la variante à laquelle ils correspondent. Pour une variante de type structure, l’écriture des noms de champs crée des liaisons que l’expression de la branche peut utiliser.

Ouvrez le fichier source :

nano src/main.rs

Remplacez entièrement la branche Move par :

        Message::Move { x, y } => println!("Move to ({x}, {y})"),

Lorsqu’un message Move est reçu, le motif associe ses champs enregistrés aux noms locaux x et y. L’expression d’affichage utilise directement ces noms : aucun accès distinct aux champs ni aucune conversion ne sont nécessaires.

Enregistrez et quittez, puis exécutez :

cargo run --quiet

La ligne du milieu doit maintenant être :

Move to (3, -2)

La valeur négative de y prouve que la branche a utilisé les données réellement contenues dans la variante, au lieu d’afficher des coordonnées fixes.

Étendre un match exhaustif

Dans cette étape, vous allez ajouter un message Pause, observer la vérification d’exhaustivité du compilateur, puis traiter cette nouvelle alternative.

Un match doit couvrir toutes les valeurs possibles de son énumération. Ainsi, lorsqu’une variante est ajoutée, chaque match concerné doit définir le comportement associé à ce nouveau cas.

Ouvrez le fichier et ajoutez cette variante immédiatement sous Stop :

nano src/main.rs
    Pause(u32),

Enregistrez et quittez, puis vérifiez le programme. Un échec est attendu :

cargo check

Repérez la partie stable du diagnostic :

error[E0004]: non-exhaustive patterns: `Message::Pause(_)` not covered

Le compilateur signale match message, car l’énumération peut désormais contenir une valeur Pause, mais aucune branche n’indique quoi faire dans ce cas.

Ouvrez à nouveau le fichier source. Ajoutez cette branche sous la branche Stop :

        Message::Pause(seconds) => println!("Pause for {seconds} seconds"),

Supprimez ensuite les deux caractères // au début de la ligne préparée process(Message::Pause(5)); dans main. Le motif donne à la valeur u32 contenue le nom local seconds.

Enregistrez et quittez, puis vérifiez et exécutez :

cargo check
cargo run --quiet

La sortie complète doit être :

Text: Maintenance at 9
Move to (3, -2)
Stop requested
Pause for 5 seconds

La vérification réussie prouve que le match est de nouveau exhaustif. La quatrième ligne confirme que la nouvelle branche a déstructuré la durée contenue dans Pause.

Résumé

Vous avez défini des variantes d’énumération unitaires, de type tuple et de type structure ; vous avez déstructuré des données contenues à l’aide de motifs match ; et vous avez utilisé l’erreur de compilateur E0004 pour étendre exhaustivement un match après l’ajout d’une nouvelle alternative.