Tous les endroits où les motifs peuvent être utilisés
Les motifs apparaissent dans de nombreux endroits en Rust, et vous en avez utilisé beaucoup sans vous en rendre compte ! Cette section va présenter les différentes situations où l'utilisation des motifs est appropriée.
Les branches des match
Comme nous l'avons vu au chapitre 6, nous utilisons les motifs dans les
branches des expressions match
. Techniquement, les expressions match
sont
définies avec le mot-clé match
, une valeur sur laquelle procéder et une ou
plusieurs branches qui constituent un motif, chacune associée à une expression
à exécuter si la valeur correspond au motif de la branche, comme ceci :
match VALEUR {
MOTIF => EXPRESSION,
MOTIF => EXPRESSION,
MOTIF => EXPRESSION,
}
L'une des conditions à respecter pour les expressions match
est qu'elles
doivent être exhaustives dans le sens où toutes les valeurs possibles de la
valeur présente dans l'expression match
doivent être prises en compte. Une
façon de s'assurer que vous avez couvert toutes les possibilités est d'avoir un
motif passe-partout pour la dernière branche : par exemple, une valeur
quelconque ne pourra jamais échouer car la dernière branche permet de couvrir
tous les autres cas possibles.
Le motif spécifique _
va correspondre à tout, mais il ne fournira jamais de
variable, donc il est souvent utilisé dans la dernière branche. Le motif _
peut par exemple être utile lorsque vous souhaitez ignorer toutes les autres
valeurs qui n'ont pas été listées. Nous allons voir plus en détail le motif _
dans une section
plus tard dans ce chapitre.
Les expressions conditionnelles if let
Au chapitre 6, nous avons vu comment utiliser les expressions if let
,
principalement pour pouvoir écrire l'équivalent d'un match
qui ne correspond
qu'à un seul cas.
Accessoirement, if let
peut avoir un else
correspondant au code à exécuter
si le motif du if let
ne correspond pas au premier critère.
L'encart 18-1 montre qu'il est aussi possible de conjuguer les expressions
if let
, else if
et else if let
. Faire ceci nous donne plus de
flexibilité qu'une expression match
dans laquelle nous ne pouvons
fournir qu'une seule valeur à comparer avec les motifs. De plus, dans une série
de branches if let
, else if
et else if let
, les conditions n'ont pas
besoin d'être en rapport les unes avec les autres.
Le code de l'encart 18-1 montre une série de vérifications pour quelques conditions qui décident quelle devrait être la couleur de fond. Pour cet exemple, nous avons créé les variables avec des valeurs codées en dur qu'un vrai programme devrait recevoir d'une saisie d'un utilisateur.
Fichier : src/main.rs
fn main() { let couleur_favorite: Option<&str> = None; let on_est_mardi = false; let age: Result<u8, _> = "34".parse(); if let Some(couleur) = couleur_favorite { println!("Utilisation de votre couleur favorite, {}, comme couleur de fond", couleur); } else if on_est_mardi { println!("Mardi, c'est le jour du vert !"); } else if let Ok(age) = age { if age > 30 { println!("Utilisation du violet comme couleur de fond"); } else { println!("Utilisation de l'orange comme couleur de fond"); } } else { println!("Utilisation du bleu comme couleur de fond"); } }
Si l'utilisateur renseigne une couleur favorite, c'est cette couleur qui devient la couleur de fond. Sinon, si nous sommes mardi, la couleur de fond sera le vert. Sinon, si l'utilisateur a renseigné son âge dans une chaîne de caractères et que nous pouvons l'interpréter comme un nombre avec succès, la couleur de fond sera soit le violet, soit l'orange en fonction de la valeur de ce nombre. Enfin, si aucune de ces conditions ne s'applique, la couleur de fond sera le bleu.
Cette structure conditionnelle nous permet de répondre à des conditions
complexes. Avec les valeurs codées en dur que nous avons ici, cet exemple
devrait afficher Utilisation du violet comme couleur de fond
.
Vous pouvez constater que le if let
nous permet d'utiliser les variables
masquées de la même manière que le font les branches match
: la ligne
if let Ok(age) = age
crée une nouvelle variable masquée age
qui contient la
valeur présente dans la variante Ok
. Cela signifie que nous devons placer la
condition if age > 30
à l'intérieur de ce bloc : nous ne pouvons pas combiner
ces deux conditions dans une seule if let Ok(age) = age && age > 30
. La
variable masquée age
que nous souhaitons comparer à 30 n'est pas encore en
vigueur tant que la nouvelle portée entre les accolades n'a pas commencée.
Le désavantage de l'utilisation des expressions if let
est que le compilateur
ne vérifie pas l'exhaustivité contrairement à une expression match
. Si nous avions
enlevé le dernier bloc else
, oubliant ainsi de gérer certains cas,
le compilateur n'aurait pas pu nous prévenir d'un possible bogue de logique.
les boucles conditionelles while let
Comme les constructions if let
, les boucles conditionnelles while let
permettent à une boucle while
de s'exécuter aussi longtemps qu'un motif
continue à correspondre. L'exemple dans l'encart 18-2 montre une boucle
while let
qui utilise un vecteur comme une pile et affiche les valeurs du
vecteur dans l'ordre opposé à celui dans lequel elles ont été insérées.
fn main() { let mut pile = Vec::new(); pile.push(1); pile.push(2); pile.push(3); while let Some(donnee_du_haut) = pile.pop() { println!("{}", donnee_du_haut); } }
Cet exemple affiche 3, 2 puis ensuite 1. La méthode pop
sort le dernier
élément du vecteur et retourne Some(valeur)
. Si le vecteur est vide, pop
retourne alors None
. La boucle while
continue à exécuter le code de son bloc
aussi longtemps que pop
retourne un Some
. Lorsque pop
retournera None
,
la boucle s'arrêtera. Nous pouvons utiliser while let
pour extraire tous les
éléments de la pile.
Les boucles for
Au chapitre 3, nous avions mentionné que la boucle for
était la construction
de boucle la plus utilisée dans du code Rust, mais nous n'avons pas encore abordé
le motif que prend for
. Dans une boucle for
, le motif est la valeur qui suit
directement le mot-clé for
, de sorte que x
est le motif dans for x in y
.
L'encart 18-3 montre comment utiliser un motif dans une boucle for
pour
déstructurer, ou décomposer, un tuple faisant partie de la boucle for
.
fn main() { let v = vec!['a', 'b', 'c']; for (indice, valeur) in v.iter().enumerate() { println!("{} est à l'indice {}", valeur, indice); } }
Le code de l'encart 18-3 va afficher ceci :
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished dev [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a est à l'indice 0
b est à l'indice 1
c est à l'indice 2
Nous avons utilisé la méthode enumerate
pour produire une valeur et son indice
à partir d'un itérateur que nous avons placé dans un tuple. La premiere valeur
produite est le tuple (0, 'a')
. Comme cette valeur correspond au motif
(indice, valeur)
, indice
se voit affecter 0
, valeur
se voit affecter 'a'
,
provoquant l'affichage de la première ligne sur la sortie.
Les instructions let
Avant d'arriver à ce chapitre, nous n'avions abordé explicitement l'utilisation
des motifs qu'avec match
et if let
, mais en réalité, nous avions utilisé
les motifs dans d'autres endroits, y compris dans les instructions let
. Par
exemple, considérons l'assignation de la variable suivante avec let
:
#![allow(unused)] fn main() { let x = 5; }
Tout au long de ce livre, nous avons utilisé let
de cette manière des
centaines de fois, et malgré tout vous ne vous êtes probablement pas rendu
compte que vous utilisiez les motifs ! Plus formellement, une instruction let
ressemble à ceci :
let MOTIF = EXPRESSION;
Dans des instructions telles que let x = 5;
avec un nom de variable dans
l'emplacement MOTIF
, le nom de la variable n'est juste qu'une forme
particulièrement simple de motif. Rust compare l'expression avec le motif et
assigne tous les noms qu'il trouve. Dans l'exemple let x = 5;
, x
est un
motif qui signifie “relie ce qui correspond ici à la variable x
”. Puisque le
nom x
constitue un motif complet, il signifie exactement “relie tout ce qui
suit à la variable x
, quelle qu'en soit la valeur”.
Pour comprendre plus clairement l'aspect filtrage par motif de let
, examinons
l'encart 18-4, qui utilise un motif let
pour destructurer un tuple.
fn main() { let (x, y, z) = (1, 2, 3); }
Ici, nous avons fait correspondre un tuple à un motif. Rust compare la valeur
(1, 2, 3)
avec le motif (x, y, z)
et constate que la valeur correspond au motif,
donc Rust relie 1
à x
, 2
à y
et 3
à z
. Vous pouvez ainsi considérer
que ce motif de tuple encapsule trois variables individuelles.
Si le nombre d'éléments dans le motif ne correspond pas au nombre d'éléments dans le tuple, le type global ne va pas correspondre et nous allons obtenir une erreur de compilation. Par exemple, l'encart 18-5 montre une tentative de déstructurer un tuple avec trois éléments dans deux variables, ce qui ne va pas fonctionner.
fn main() {
let (x, y) = (1, 2, 3);
}
Si vous essayez de compiler ce code, vous obtiendrez cette erreur de type :
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` due to previous error
Si nous souhaitons ignorer une ou plusieurs valeurs dans un tuple, nous pouvons
utiliser _
ou ..
, comme vous allez le voir à la dernière section de ce
chapitre. Si le problème est que nous avons trop de variables dans le motif, la
solution pour faire correspondre les types consiste à enlever des variables de
façon à ce que le nombre de variables corresponde au nombre d'éléments présents
dans le tuple.
Les paramètres de fonctions
Les paramètres de fonctions peuvent aussi être des motifs. Le code de l'encart
18-6 déclare une fonction foo
qui prend un paramètre x
de type i32
.
fn fonction(x: i32) { // le code se place ici } fn main() {}
La partie x
est un motif ! Comme nous l'avons dit pour let
, nous pouvons
faire correspondre le motif avec un tuple dans les arguments de la fonction.
L'encart 18-7 déstructure les valeurs d'un tuple que nous passons en argument
d'une fonction.
Fichier : src/main.rs
fn afficher_coordonnees(&(x, y): &(i32, i32)) { println!("Coordonnées actuelles : ({}, {})", x, y); } fn main() { let point = (3, 5); afficher_coordonnees(&point); }
Ce code affiche Coordonées actuelles : (3, 5)
. Les valeurs &(3, 5)
correspondent au motif &(x, y)
, donc x
a la valeur 3
et y
a la valeur
5
.
Nous pouvons aussi utiliser les motifs dans la liste des paramètres d'une fermeture de la même manière que dans la liste des paramètres d'une fonction, car les fermetures sont similaires aux fonctions, comme nous l'avons dit au chapitre 13.
A présent, vous avez vu plusieurs façons d'utiliser les motifs, mais les motifs ne fonctionnent pas de la même manière dans toutes les situations où nous les utilisons. Des fois, le motif sera irréfutable ; d'autres fois, il sera réfutable. C'est ce que nous allons voir tout de suite.