Une structure de contrôle concise : if let
La syntaxe if let vous permet de combiner if et let afin de gérer les
valeurs qui correspondent à un motif donné, tout en ignorant les autres.
Imaginons le programme dans l'encart 6-6 qui fait un match sur la valeur
Option<u8> de la variable une_valeur_u8 mais n'a besoin d'exécuter du code que
si la valeur est la variante Some.
fn main() { let une_valeur_u8 = Some(3u8); match une_valeur_u8 { Some(max) => println!("Le maximum est réglé sur {}", max), _ => (), } }
Encart 6-6 : Un match qui n'exécute du code que si la
valeur est Some
Si la valeur est un Some, nous affichons la valeur dans la variante Some en
associant la valeur à la variable max dans le motif. Nous ne voulons rien
faire avec la valeur None. Pour satisfaire l'expression match, nous devons
ajouter _ => () après avoir géré une seule variante, ce qui est du code
inutile.
À la place, nous pourrions écrire le même programme de manière plus concise en
utilisant if let. Le code suivant se comporte comme le match de l'encart
6-6 :
fn main() { let une_valeur_u8 = Some(3u8); if let Some(max) = une_valeur_u8 { println!("Le maximum est réglé sur {}", max); } }
La syntaxe if let prend un motif et une expression séparés par un signe égal.
Elle fonctionne de la même manière qu'un match où l'expression est donnée au
match et où le motif est sa première branche. Dans ce cas, le motif est
Some(max), et le max est associé à la valeur dans le Some. Nous pouvons
ensuite utiliser max dans le corps du bloc if let de la même manière que
nous avons utilisé max dans la branche correspondante au match. Le code dans
le bloc if let n'est pas exécuté si la valeur ne correspond pas au motif.
Utiliser if let permet d'écrire moins de code, et de moins l'indenter.
Cependant, vous perdez la vérification de l'exhaustivité qu'assure le match.
Choisir entre match et if let dépend de la situation : à vous de choisir
s'il vaut mieux être concis ou appliquer une vérification exhaustive.
Autrement dit, vous pouvez considérer le if let comme du sucre syntaxique pour
un match qui exécute du code uniquement quand la valeur correspond à un motif
donné et ignore toutes les autres valeurs.
Nous pouvons joindre un else à un if let. Le bloc de code qui va dans le
else est le même que le bloc de code qui va dans le cas _ avec l'expression
match. Souvenez-vous de la définition de l'énumération PieceUs de l'encart
6-4, où la variante Quarter stockait aussi une valeur EtatUs. Si nous
voulions compter toutes les pièces qui ne sont pas des quarters que nous
voyons passer, tout en affichant l'État des quarters, nous pourrions le faire
avec une expression match comme ceci :
#[derive(Debug)] enum EtatUs { Alabama, Alaska, // -- partie masquée ici -- } enum PieceUs { Penny, Nickel, Dime, Quarter(EtatUs), } fn main() { let piece = PieceUs::Penny; let mut compteur = 0; match piece { PieceUs::Quarter(etat) => println!("Il s'agit d'un quarter de l'État de {:?} !", etat), _ => compteur += 1, } }
Ou nous pourrions utiliser une expression if let/else comme ceci :
#[derive(Debug)] enum EtatUs { Alabama, Alaska, // -- partie masquée ici -- } enum PieceUs { Penny, Nickel, Dime, Quarter(EtatUs), } fn main() { let piece = PieceUs::Penny; let mut compteur = 0; if let PieceUs::Quarter(etat) = piece { println!("Il s'agit d'un quarter de l'État de {:?} !", etat); } else { compteur += 1; } }
Si vous trouvez que votre programme est alourdi par l'utilisation d'un match,
souvenez-vous que if let est aussi présent dans votre boite à outils Rust.
Résumé
Nous avons désormais appris comment utiliser les énumérations pour créer des
types personnalisés qui peuvent faire partie d'un jeu de valeurs recensées. Nous
avons montré comment le type Option<T> de la bibliothèque standard vous aide
à utiliser le système de types pour éviter les erreurs. Lorsque les valeurs
d'énumération contiennent des données, vous pouvez utiliser match ou if let
pour extraire et utiliser ces valeurs, à choisir en fonction du nombre de cas
que vous voulez gérer.
Vos programmes Rust peuvent maintenant décrire des concepts métier à l'aide de structures et d'énumérations. Créer des types personnalisés à utiliser dans votre API assure la sécurité des types : le compilateur s'assurera que vos fonctions ne reçoivent que des valeurs du type attendu.
Afin de fournir une API bien organisée, simple à utiliser et qui n'expose que ce dont vos utilisateurs auront besoin, découvrons maintenant les modules de Rust.