7. Contrôle du flux▲
La caractéristique commune à tous langages est la capacité à contrôler le flux : if
/else
, for
, etc., et Rust ne fait pas exception. Allons voir ça !
7-1. If/else▲
Les branchements conditionnels tels que if
ou else
sont similaires à d'autres langages. Contrairement à beaucoup d'entre-eux, la condition booléenne peut toutefois ne pas être enveloppée de parenthèses et chaque condition est suivie d'un bloc. Les conditions if
/else
sont des expressions et toutes les branches doivent renvoyer le même type.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
fn
main() {
let
n = 5
;
if
n < 0
{
print!
("
{}
est négatif."
, n);
} else
if
n > 0
{
print!
("
{}
est positif."
, n);
} else
{
print!
("
{}
est nul."
, n);
}
let
big_n =
if
n < 10
&&
n > -10
{
println!
(" et est un petit nombre, multiplions-le par dix"
);
// Cette expression renvoie un entier de type `i32`.
10
* n
} else
{
println!
(" est un grand nombre, divisons-le par deux"
);
// Cette expression doit également renvoyer un entier de type `i32`.
n / 2
// TODO ^ Essayez de supprimer cette expression en ajoutant un point-virgule.
};
// ^ Ne pas oubliez de mettre un point-virgule ici! Toutes les
// assignations (`let`) doivent se terminer par un point-virgule.
println!
("
{}
->
{}
"
, n, big_n);
}
7-2. Le mot-clé loop▲
Rust fournit le mot-clé loop
pour créer une boucle infinie.
Le mot-clé break
peut être utilisé pour sortir de la boucle n'importe où tandis que le mot-clé continue
peut être utilisé pour ignorer le reste de l'itération en cours et en débuter une nouvelle.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
fn
main() {
let
mut
count = 0
u32;
println!
("Comptons jusqu'à l'infini!"
);
// Boucle infinie.
loop
{
count += 1
;
if
count == 3
{
println!
("trois"
);
// Ignore le reste de l'itération.
continue
;
}
println!
("
{}
"
, count);
if
count == 5
{
println!
("Ok, ça suffit!"
);
// Sort de la boucle.
break
;
}
}
}
7-2-1. L'imbrication et les labels▲
Il est possible de sortir (i.e. break
) ou de relancer (i.e. continue
) l'itération d'une boucle à partir d'une autre boucle interne à cette dernière. Pour ce faire, les boucles concernées doivent être annotées avec un 'label
et il devra être passé aux instructions break
et/ou continue
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
#![allow(unreachable_code)]
// permet de faire taire les avertissements
// relatifs au code mort.
fn
main() {
'externe
: loop
{
println!
("Entré dans la boucle annotée 'externe."
);
'interne
: loop
{
println!
("Entré dans la boucle annotée 'interne."
);
// Cette instruction nous ferait simplement
// sortir de la boucle 'interne.
// break;
// On sort de la boucle 'externe
// à partir de la boucle 'interne.
break
'externe
;
}
println!
("Cette ligne ne sera jamais exécutée."
);
}
println!
("Sorti de la boucle annotée 'externe."
);
}
7-3. La boucle while▲
Le mot-clé while
peut être utilisé pour itérer jusqu'à ce qu'une condition soit remplie.
Écrivons les règles de l'infâme FizzBuzz en utilisant une boucle while
:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
fn
main() {
// Un compteur.
let
mut
n = 1
;
// Itère sur `n` tant que sa valeur est strictement inférieure
// à 101.
while
n < 101
{
if
n % 15
== 0
{
println!
("fizzbuzz"
);
} else
if
n % 3
== 0
{
println!
("fizz"
);
} else
if
n % 5
== 0
{
println!
("buzz"
);
} else
{
println!
("
{}
"
, n);
}
// Incrémente le compteur.
n += 1
;
}
}
7-4. La boucle for et les invervalles▲
L'ensemble for
in
peut être utilisé pour itérer à l'aide d'une instance Iterator. L'une des manières les plus simples pour créer un itérateur est d'utiliser la notation d'intervalle (« range notation ») a..b. Soit un intervalle [a;b[ (comprend toutes les valeurs entre a (inclut) et b (exclut)).
Écrivons les règles de FizzBuzz en utilisant la boucle for
au lieu de while
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
fn
main() {
// `n` prendra pour valeur: 1, 2, ..., 100 au fil des itérations.
for
n in
1
..101
{
if
n % 15
== 0
{
println!
("fizzbuzz"
);
} else
if
n % 3
== 0
{
println!
("fizz"
);
} else
if
n % 5
== 0
{
println!
("buzz"
);
} else
{
println!
("
{}
"
, n);
}
}
}
Voir aussi
7-5. Le pattern matching▲
Rust fournit le pattern matching via le mot-clé match
, lequel peut être utilisé comme le mot-clé switch avec le langage C.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
fn
main() {
let
number = 13
;
// TODO ^ Assignez différentes valeurs à `number`.
println!
("Nature de number
{}
"
, number);
match
number {
// Teste une seule valeur.
1
=> println!
("Un!"
),
// Teste plusieurs valeurs.
2
| 3
| 5
| 7
| 11
=> println!
("C'est un nombre premier."
),
// Teste l'intervalle [13;19].
13
...19
=> println!
("A teen"
),
// Couvre tous les autres cas.
_ => println!
("Ain't special"
),
}
let
boolean = true
;
// L'analyse est également une expression.
let
binary = match
boolean {
// Les branches du match doivent couvrir tous les cas possibles.
false
=> 0
,
true
=> 1
,
// TODO ^ Essayez de commenter l'une de ses branches.
};
println!
("
{}
->
{}
"
, boolean, binary);
}
7-5-1. La déstructuration▲
Un bloc match
peut décomposer des objets de différentes manières.
7-5-1-1. Les tuples▲
Les tuples peuvent être déstructurés (entendez « décortiqués », « décomposés ») dans un bloc match
comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
fn
main() {
let
pair = (0
, -2
);
// TODO ^ Essayez de modifier les valeurs contenues par
// le tuple.
println!
("Dites m'en plus à propos de
{:?}
"
, pair);
// match peut être utilisé pour déstructurer un tuple.
match
pair {
// Déstructure `y`.
(0
, y) => println!
("Le premier élément est égal à `0`
et `y` égal à `
{:?}
`"
, y),
(x, 0
) => println!
("`x` est égal à `
{:?}
` et le dernier est égal à `0`"
, x),
_ => println!
("Peu importe ce qu'ils sont."
),
// L'underscore `_` siginifie que vous ne souhaitez pas
// assigner de valeurs à une variable, que vous souhaitez couvrir tous les
// autres cas.
}
}
Voir aussi
7-5-1-2. Les énumérations▲
Une énumération est déstructurée de la même manière :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
// On fait taire les avertissements (puisqu'on utilise
// qu'une seule variante).
#[allow(dead_code)]
enum
Color {
// Identification implicite.
Red,
Blue,
Green,
// Ces variantes assignent plusieurs tuples sous différents noms: les modèles
// de couleur.
RGB(u32, u32, u32),
HSV(u32, u32, u32),
HSL(u32, u32, u32),
CMY(u32, u32, u32),
CMYK(u32, u32, u32, u32),
}
fn
main() {
let
color = Color::RGB(122
, 17
, 40
);
// TODO ^ Essayez de modifier les valeurs du tuple.
println!
("De quelle couleur s'agit-il?"
);
// Une énumération peut être déstructurée en utilisant le pattern matching.
match
color {
Color::Red => println!
("La couleur rouge!"
),
Color::Blue => println!
("La couleur bleu!"
),
Color::Green => println!
("La couleur vert!"
),
Color::RGB(r, g, b) =>
println!
("Rouge:
{}
, Vert:
{}
, et Bleu:
{}
!"
, r, g, b),
Color::HSV(h, s, v) =>
println!
("Teinte:
{}
, Saturation:
{}
, Valeur:
{}
!"
, h, s, v),
Color::HSL(h, s, l) =>
println!
("Teinte:
{}
, Saturation:
{}
, Lumière:
{}
!"
, h, s, l),
Color::CMY(c, m, y) =>
println!
("Cyan:
{}
, Magenta:
{}
, Jaune:
{}
!"
, c, m, y),
Color::CMYK(c, m, y, k) =>
println!
("Cyan:
{}
, Magenta:
{}
, Jaune:
{}
, Noir:
{}
!"
,
c, m, y, k),
// Inutile d'ajouter une branche "par défaut" car tous les
// cas ont été couverts.
}
}
Voir aussi
L'attribut allow(...), les modèles de couleur FR ou EN et les énumérations.
7-5-1-3. Les pointeurs et références▲
À propos des pointeurs, la distinction doit être faite entre la déstructuration et le déréférencement puisque ce sont deux concepts différents utilisés différemment par rapport au langage C.
- Le déréférencement utilise * ;
- La déstructuration utilise
&
,ref
, etref
mut
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
fn
main() {
// Assigne une référence de type `i32`. Le `&` signifie qu'une
// référence est assignée.
// L'équivalent non-raccourci de cette assignation pourrait ressembler à ceci:
// ```rust
// let _reference: i32 = 4;
// let reference: &i32 = &_reference;
// ```
let
reference: &
i32 = &
4
;
match
reference {
// Lorsque `reference` est comparé à `&val`, la comparaison ressemble à ceci:
// `&i32`
// `&val` <- `val` est plus ou moins une représentation de `reference`.
// ^ Nous remarquons que si le `&` est omis, la valeur devrait être
// assignée à `val`.
&
val => println!
("On récupère une valeur via déstructuration:
{:?}
"
, val),
}
// Pour éviter d'utiliser la référence, vous pouvez déréférencer `reference`
// avant analyse (vous permettant d'opérer sur la valeur, si elle est mutable).
match
*reference {
val => println!
("On récupère la valeur déréférencée:
{:?}
"
, val),
}
// Que se passe-t-il si vous ne créez pas une référence ? `reference`
// était une référence parce que la r-value était une référence. Cette
// variable n'en est pas une parce que la r-value n'en est pas une.
let
_not_a_reference = 3
;
// Rust fournit le mot-clé `ref` dans ce but. Il modifie l'assignation
// de manière à créer une référence pour l'élément; cette référence est assignée.
let
ref
_is_a_reference = 3
;
// Bien entendu, en assignant deux valeurs sans références, ces dernières
// peuvent être récupérées à l'aide du mot-clé `ref` et `ref mut`.
let
value = 5
;
let
mut
mut_value = 6
;
// On utilise le mot-clé `ref` pour créer une référence.
match
value {
ref
r => println!
("On récupère une référence de la valeur:
{:?}
"
, r),
}
// `ref mut` s'utilise de la même manière.
match
mut_value {
ref
mut
m => {
// On obtient une référence. Nous allons déréférencer `m` avant
// de pouvoir opérer.
*m += 10
;
println!
("Nous incrémentons de 10. `mut_value`:
{:?}
"
, m);
},
}
}
7-5-1-4. Les structures▲
Une structure peut également être déstructurée comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
fn
main() {
struct
Foo { x: (u32, u32), y: u32 }
// Déstructure les membres de la structure.
let
foo = Foo { x: (1
, 2
), y: 3
};
let
Foo { x: (a, b), y } = foo;
println!
("a =
{}
, b =
{}
, y =
{}
"
, a, b, y);
// Vous pouvez déstructurer les structures et renommer
// leurs variables. L'ordre n'est pas important.
let
Foo { y: i, x: j } = foo;
println!
("i =
{:?}
, j =
{:?}
"
, i, j);
// et vous pouvez aussi ignorer certaines variables:
let
Foo { y, .. } = foo;
println!
("y =
{}
"
, y);
// Ceci donne une erreur: le pattern ne mentionne pas le champ `x`.
// let Foo { y } = foo;
}
Voir aussi
7-5-2. Les gardes▲
Lorsque vous usez du pattern matching, un « garde » peut être ajouté dans chaque branche du match
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
fn
main() {
let
pair = (2
, -2
);
// TODO ^ Essayez de modifier les valeurs de `pair`.
println!
("Dites m'en plus à propos de:
{:?}
"
, pair);
match
pair {
(x, y) if
x == y => println!
("Ils sont jumeaux!"
),
// La ^ condition if est un garde.
(x, y) if
x + y == 0
=> println!
("De l'antimatière, boom!"
),
(x, _) if
x % 2
== 1
=> println!
("Le premier est étrange..."
),
_ => println!
("Rien de spécial..."
),
}
}
Voir aussi
7-5-3. Assignation▲
Accéder indirectement à une variable rend impossible sa réutilisation sans la réassigner. match
fournit le symbole @ pour assigner des valeurs à des identificateurs :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
// Une fonction `age` qui renvoie un entier de type `u32`.
fn
age() -> u32 {
15
}
fn
main() {
println!
("Dis-moi quel type de personne es-tu."
);
match
age() {
0
=> println!
("Je ne suis pas encore né, je suppose."
),
// Nous aurions pu `match` 1 ... 12 directement mais il n'aurait pas
// été possible de connaître l'âge de l'enfant
// À la place, nous assignons la valeur à `n`
// pour la séquence de 1 ... 12. L'âge peut désormais être affiché.
n @ 1
... 12
=> println!
("Je suis un enfant de
{:?}
ans!"
, n),
n @ 13
... 19
=> println!
("Je suis un adolescent de
{:?}
ans!"
, n),
// Pas de limite. On renvoie le résultat.
n => println!
("Je suis un adulte de
{:?}
ans!"
, n),
}
}
Voir aussi
7-6. if let▲
Pour certains cas, match
peut être « lourd ». Par exemple :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
// Crée une valeur optionnelle de type `Option<i32>`.
let
optional = Some
(7
);
match
optional {
Some
(i) => {
println!
("Ceci est une très longue chaîne de caractères contenant un
`
{:?}
`"
, i);
// ^ Deux niveaux d'indentations sont nécessaires alors
// que nous aurions pu simplement déstructurer `i`.
},
_ => {},
// ^ Nécessaire parce que `match` est exhaustif. Cette branche vous
// paraît-elle utile?
};
if
let
est plus adapté à ce genre de cas et permet la création de plusieurs branches en cas d'erreur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
fn
main() {
// Toutes les variables sont de type `Option<i32>`.
let
number = Some
(7
);
let
letter: Option
<i32> = None
;
let
emoticon: Option
<i32> = None
;
// L'ensemble `if let` se déroule de cette manière:
// `if let` déstructure `number` et assigne sa valeur à `i` et exécute
// le bloc (`{}`).
if
let
Some
(i) = number {
println!
("
{:?}
a été trouvé!"
, i);
}
// Si vous devez spécifier un cas d'erreur, utilisez un `else`:
if
let
Some
(i) = letter {
println!
("
{:?}
a été trouvé!"
, i);
} else
{
// Déstructuration ratée. On exécute le `else`.
println!
("Aucun nombre n'a été trouvé.
Cherchons une lettre!"
);
};
// Fournit une condition alternative.
let
i_like_letters = false
;
if
let
Some
(i) = emoticon {
println!
("
{:?}
a été trouvé!"
, i);
// Déstructuration ratée. Passe à une condition `else if` pour tester si
// la condition alternative est vraie.
} else
if
i_like_letters {
println!
("Aucun nombre n'a été trouvé.
Cherchons une lettre!"
);
} else
{
// La condition évaluée est fausse. Branche par défaut:
println!
("Je n'aime pas les lettres. Cherchons une emoticône :)!"
);
};
}
Voir aussi
7-7. while let▲
Ayant un fonctionnement similaire à if
let
, while
let
peut alléger la syntaxe de match
lorsqu'il n'est pas nécessaire de passer par le pattern matching. Voici une séquence qui incrémente i :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
// Crée une valeur optionnelle de type `Option<i32>`.
let
mut
optional = Some
(0
);
// On répète le test.
loop
{
match
optional {
// Si il est possible de déstructurer `optional`,
// le bloc sera exécuté.
Some
(i) => {
if
i > 9
{
println!
("Plus grand que 9, on quitte!"
);
optional = None
;
} else
{
println!
("`i` est égal à `
{:?}
`. On réitère."
, i);
optional = Some
(i + 1
);
}
// ^ Nécessite trois niveaux d'indentations.
},
// On quitte la boucle si la déstructuration
// a échoué:
_ => { break
; }
// ^ Pourquoi cette instruction devrait être nécessaire ?
// Il doit y avoir une solution plus adaptée!
}
}
En utilisant while
let
, cela rend la séquence plus lisible :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
fn
main() {
// Crée une valeur optionnelle de type `Option<i32>`.
let
mut
optional = Some
(0
);
// Fonctionnement: "`while let` déstructure `optional` pour assigner sa valeur
// à Some(i) puis exécute le bloc (`{}`). Sinon, on sort de la boucle."
while
let
Some
(i) = optional {
if
i > 9
{
println!
("Plus grand que 9, on quitte!"
);
optional = None
;
} else
{
println!
("`i` est égal à `
{:?}
`. On réitère."
, i);
optional = Some
(i + 1
);
}
// Moins explicite, il n'est plus nécessaire de gérer
// le cas où la déstructuration échoue.
}
// ^ `if let` permet d'ajouter des branches `else`/`else if`
// optionnelles. `while let` ne le permet pas, en revanche.
}
Voir aussi
Les énumérations, l'énumération Option et la RFC de while let.