backend / rust
Pattern matching avec match
Explication
Ce que vous allez apprendre
- Écrire un
matchexhaustif 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 avecif letetwhile 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 pattern | Syntaxe | Usage |
|---|---|---|
| Plage | 0..=39 => "insuffisant" | Regrouper un intervalle de valeurs |
| Alternative | 80, 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é.
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é
matchdoit 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 letsimplifient unmatchqui ne s'intéresse qu'à un seul pattern.- Le
@binding capture une valeur tout en la filtrant dans le même bras.
Exercices pratiques
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.