IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Rust par l'exemple


précédentsommairesuivant

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.

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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 = 0u32;

    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.

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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.

 
Sélectionnez
1.
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

Les itérateurs.

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.

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Les tuples.

7-5-1-2. Les énumérations

Une énumération est déstructurée de la même manière :

 
Sélectionnez
1.
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, et ref mut.
 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Les structures, ref.

7-5-2. Les gardes

Lorsque vous usez du pattern matching, un « garde » peut être ajouté dans chaque branche du match.

 
Sélectionnez
1.
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

Les tuples.

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 :

 
Sélectionnez
1.
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

Les fonctions.

7-6. if let

Pour certains cas, match peut être « lourd ». Par exemple :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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

Les énumérations, l'énumération Option et la RFC de if let.

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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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.


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Rust Core Team et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.