Aller au contenu

If Let et While Let en Rust

Vous avez utilisé match pour traiter Option et Result. Pourtant, la plupart du temps, un seul motif vous intéresse vraiment et vous voulez ignorer tout le reste. Écrire un match complet avec une branche _ => {} qui ne fait rien fonctionne, mais c’est beaucoup de cérémonie pour pas grand-chose. if let est le raccourci.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("L'âge est {value}"),
_ => {}, // ce cas ne nous intéresse pas vraiment
}
}

Ce _ => {} ne fait rien, à part satisfaire la règle de match selon laquelle « tous les cas doivent être traités ». Si vous voulez seulement agir sur Some, c’est plus de code que l’idée n’en mérite.

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("L'âge est {value}");
}
}

Sortie :

L'âge est 30
▶ Essayez par vous-même

Lisez-le ainsi : « si age correspond au motif Some(value), exécute ce bloc, avec value qui contient ce qu’il y avait à l’intérieur ». Si age vaut None, le bloc est simplement sauté : pas d’erreur, rien n’est affiché.

if let peut avoir un else, exactement comme un if normal :

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("L'âge est {value}");
} else {
println!("Aucun âge indiqué");
}
}
▶ Essayez par vous-même

Cela fonctionne de la même façon avec Result, que vous avez rencontré sur la page Result :

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("Converti : {number}");
} else {
println!("Impossible de convertir ce texte");
}
}
▶ Essayez par vous-même

Comme if a else if, if let peut être suivi de else if let. Rust essaie chaque motif à tour de rôle et exécute le premier bloc qui correspond. C’est pratique quand vous avez plusieurs endroits où chercher une valeur, par ordre de préférence :

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Ferris Crab");
if let Some(nick) = nickname {
println!("Salut, {nick} !");
} else if let Some(name) = full_name {
println!("Bonjour, {name}.");
} else {
println!("Bonjour, inconnu.");
}
}

Sortie :

Bonjour, Ferris Crab.
▶ Essayez par vous-même

nickname vaut None, donc le premier bloc est sauté. full_name correspond à Some(name), donc ce bloc s’exécute, et le else final n’est jamais atteint. Vous pouvez aussi glisser une condition ordinaire, comme else if is_admin { ... }, n’importe où dans la chaîne.

Chaque variable n’existe que dans son propre bloc : nick n’est pas utilisable dans le deuxième bloc, et name n’est plus utilisable après la fin de la chaîne. Si vous avez besoin de la valeur déballée pour tout le reste de votre code, regardez let-else sur la page Le pattern matching.

while let applique la même idée à une boucle : on continue tant qu’une valeur correspond à un motif. C’est particulièrement pratique pour vider une collection :

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

Sortie :

3
2
1
▶ Essayez par vous-même

.pop() renvoie Some(value) tant qu’il reste quelque chose, puis None une fois le vecteur vide. À ce moment-là, while let arrête la boucle tout seul. Pas besoin de vérifier la longueur à la main.

Utilisez match quand vous devez vraiment traiter plusieurs cas de façons différentes. Préférez if let quand un seul motif vous intéresse et que tout le reste doit simplement être ignoré. Ce n’est pas un remplaçant de match : c’est juste une façon plus courte d’écrire le cas fréquent « seule une branche m’intéresse ».

Partez de let mut numbers = vec![4, 8, 15]; et d’un total courant total à 0. Utilisez while let avec .pop() pour retirer les nombres de la fin un par un, ajoutez chacun à total, et affichez le nombre ainsi que le nouveau total. Indice : total doit être mut, et total += n lui ajoute une valeur.

Voir la solution
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("retiré {n}, le total vaut maintenant {total}");
}
}

Sortie :

retiré 15, le total vaut maintenant 15
retiré 8, le total vaut maintenant 23
retiré 4, le total vaut maintenant 27
▶ Essayez par vous-même

.pop() retire l’élément de la fin, c’est pourquoi 15 sort en premier.

Stockez let input = "17";. Si le texte se convertit en u32, affichez si cet âge correspond à un majeur (18 ans ou plus) ou à un mineur. S’il ne se convertit pas, affichez que ce n’est pas un nombre. Essayez aussi de remplacer input par "abc". Indice : placez un if / else normal à l’intérieur du bloc if let.

Voir la solution
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age} ans : majeur");
} else {
println!("{age} ans : mineur");
}
} else {
println!("'{input}' n'est pas un nombre");
}
}

Sortie :

17 ans : mineur
▶ Essayez par vous-même

Avec input = "abc", la conversion échoue, le else extérieur s’exécute et le programme affiche 'abc' n'est pas un nombre.

  • if let Motif = valeur { ... } n’exécute le bloc que si valeur correspond à Motif ; tout le reste est ignoré sans bruit.
  • Ajoutez else pour traiter le cas où ça ne correspond pas, comme avec un if normal, et enchaînez plusieurs motifs avec else if let.
  • while let Motif = valeur { ... } boucle tant que la valeur continue de correspondre ; pratique pour vider une collection avec .pop().
  • Utilisez if let/while let quand un seul motif vous intéresse ; utilisez match quand vous devez traiter plusieurs cas distincts.

Petit quiz

1. Que se passe-t-il quand if let Some(value) = age s'exécute et que age vaut None ?

2. Quand faut-il préférer match à if let ?

3. Dans while let Some(top) = stack.pop() { ... }, quand la boucle s'arrête-t-elle ?

Score : 0 / 3