Retour au cours

backend / rust

Pattern matching avec match

Leçon 81 exercice

Explication

Ce que vous allez apprendre

  • Écrire un match exhaustif sur un enum, avec destructuration des données portées
  • Utiliser des plages (0..=39) et des alternatives (|) pour des cas groupés
  • Ajouter des conditions supplémentaires avec des guards (if condition)
  • Simplifier un match à un seul cas avec if let et while let
  • Capturer une valeur tout en la filtrant avec le @ binding

Dans quel contexte ?

Un développeur code une fonction qui attribue une mention selon une note (insuffisant, passable, bien, très bien, excellent). Écrite avec une longue chaîne de if/else if, cette logique devient vite difficile à lire et à maintenir à chaque nouvelle tranche ajoutée. Réécrite avec match et des plages (60..=79 => "bien"), la même logique devient immédiatement plus claire et surtout, le compilateur garantit qu'aucun cas n'a été oublié.

D'abord, il faut intégrer la garantie la plus forte de match

Contrairement à un switch dans la plupart des langages, un match Rust doit être exhaustif : le compilateur refuse de compiler si un cas possible n'est pas couvert, sauf présence d'un _ de secours. Cette contrainte, qui peut sembler stricte au début, élimine un bug très courant ailleurs : oublier de traiter un cas qui finit par se produire en production.

Une fois cette garantie acquise, match révèle une expressivité rare

Au-delà de la comparaison simple à une valeur, match peut destructurer directement les données portées par une variante d'enum (Commande::Deplacer { x, y } => ...), matcher des plages numériques (0..=39), combiner plusieurs valeurs avec |, et même destructurer un tuple ou une struct directement dans le motif.

Forme de patternSyntaxeUsage
Plage0..=39 => "insuffisant"Regrouper un intervalle de valeurs
Alternative80, 81 ou 82 => "tres bien"Regrouper plusieurs valeurs discrètes avec l'opérateur d'alternative
Guard(x, y) if x == y => "diagonale"Ajouter une condition arbitraire au motif
Binding @id @ 1..=5 => println!("{}", id)Capturer la valeur tout en la testant

Prérequis

Il faut connaître les structs et enums (leçon précédente) : le pattern matching s'exerce très souvent sur leurs variantes et leurs champs.

Il reste deux raccourcis pour les cas où match semble excessif

Quand un seul motif intéresse réellement le code, écrire un match complet avec un cas _ => {} vide devient verbeux. if let Some(n) = peut_etre_nombre { ... } exprime la même idée de façon plus concise : "si cette valeur correspond à ce motif, exécute ce bloc". while let applique la même logique en boucle, très utile pour dépiler une collection tant qu'elle contient des éléments.

Piège fréquent

if let sans else ignore silencieusement le cas où le motif ne correspond pas — ce qui est parfaitement voulu dans certains cas, mais peut aussi masquer un bug si le développeur pensait, à tort, avoir couvert tous les cas possibles. Vérifie toujours si un else explicite serait plus sûr avant d'opter pour if let seul.

Enfin, un détail syntaxique élégant : le @ binding

id @ 1..=5 => println!("id dans la plage basse: {}", id) fait deux choses en une seule expression : vérifier que la valeur appartient à la plage 1..=5, et la capturer dans une variable id utilisable dans le corps du bras. Sans ce binding, il faudrait souvent répéter la valeur testée d'une façon moins lisible.

Bonne pratique

Préfère toujours match (ou if let/while let) à une chaîne de if/else if dès que plus de deux ou trois cas sont en jeu : la garantie d'exhaustivité du compilateur vaut largement la légère verbosité initiale.

Maintenant que tu maîtrises le pattern matching, la prochaine leçon explore les deux collections les plus utilisées de la bibliothèque standard : Vec et HashMap.

Commandes & code

Pattern matching avec match

match est exhaustif : le compilateur refuse la compilation si un cas est oublié.

rust
enum Commande {
    Deplacer { x: i32, y: i32 },
    Ecrire(String),
    Couleur(u8, u8, u8),
    Quitter,
}

fn traiter(cmd: Commande) {
    match cmd {
        Commande::Deplacer { x, y } => println!("deplacement vers ({}, {})", x, y),
        Commande::Ecrire(texte) => println!("ecriture: {}", texte),
        Commande::Couleur(r, g, b) => println!("couleur rgb({}, {}, {})", r, g, b),
        Commande::Quitter => println!("arret"),
        // pas de _ requis : les 4 variantes sont couvertes, match exhaustif
    }
}

fn main() {
    traiter(Commande::Deplacer { x: 10, y: 20 });
    traiter(Commande::Ecrire(String::from("hello")));

    // match sur des valeurs simples avec plages et alternatives
    let note = 82;
    let mention = match note {
        0..=39 => "insuffisant",
        40..=59 => "passable",
        60..=79 => "bien",
        80 | 81 | 82 | 83 => "tres bien", // alternative avec |
        84..=100 => "excellent",
        _ => "invalide", // _ : joker, obligatoire si les cas ne sont pas exhaustifs
    };
    println!("{}", mention);

    // Destructuration de tuples et guards (conditions supplementaires)
    let point = (3, -7);
    match point {
        (0, 0) => println!("origine"),
        (x, 0) => println!("sur l'axe X a {}", x),
        (0, y) => println!("sur l'axe Y a {}", y),
        (x, y) if x == y => println!("sur la diagonale"),
        (x, y) if x > 0 && y > 0 => println!("quadrant 1"),
        (x, y) => println!("point quelconque ({}, {})", x, y),
    }

    // if let : version courte de match quand un seul cas interesse
    let peut_etre_nombre: Option<i32> = Some(42);
    if let Some(n) = peut_etre_nombre {
        println!("nombre trouve: {}", n);
    } else {
        println!("rien");
    }

    // while let : boucler tant qu'un pattern matche
    let mut pile = vec![1, 2, 3, 4, 5];
    while let Some(top) = pile.pop() {
        println!("depile: {}", top);
    }

    // @ binding : capturer une valeur tout en la testant
    let msg_id = 7;
    match msg_id {
        id @ 1..=5 => println!("id dans la plage basse: {}", id),
        id @ 6..=10 => println!("id dans la plage haute: {}", id),
        id => println!("autre id: {}", id),
    }

    // Destructuration de struct dans un match
    struct Coord { x: i32, y: i32 }
    let c = Coord { x: 0, y: 5 };
    match c {
        Coord { x: 0, y } => println!("sur l'axe Y a {}", y),
        Coord { x, y: 0 } => println!("sur l'axe X a {}", x),
        Coord { x, y } => println!("point ({}, {})", x, y),
    }
}

Résumé

  • match doit couvrir tous les cas possibles : le compilateur refuse un match non exhaustif.
  • Guards (if condition), alternatives (|) et plages (0..=39) rendent le matching très expressif.
  • if let/while let simplifient un match qui ne s'intéresse qu'à un seul pattern.
  • Le @ binding capture une valeur tout en la filtrant dans le même bras.

Exercices pratiques

1 disponible
1

Mission : classifier des transactions bancaires suspectes

Objectif : Concevoir un match exhaustif avec plages et guards pour classifier des transactions, et diagnostiquer un if let mal utilise.

Contexte

Un systeme anti-fraude doit classer chaque transaction selon son montant en centimes (i64, peut etre negatif pour un retrait) : 'remboursement' si negatif, 'normale' de 0 a 99999, 'a verifier' de 100000 a 999999, et 'suspecte' au-dela. Un dev junior a code cette logique avec if let et se plaint que les transactions suspectes ne sont jamais loggees en production.

Résoudre l’exercice →