Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tous les endroits où les motifs peuvent être utilisés

Les motifs apparaissent à de nombreux endroits en Rust, et vous les avez beaucoup utilisés sans vous en rendre compte ! Cette section présente tous les endroits où les motifs sont valides.

Les branches de match

Comme nous l’avons vu au chapitre 6, nous utilisons des motifs dans les branches des expressions match. Formellement, les expressions match sont définies par le mot-clé match, une valeur sur laquelle effectuer la correspondance, et une où plusieurs branches composées d’un motif et d’une expression à exécuter si la valeur correspond au motif de cette branche, comme ceci :

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

Par exemple, voici l’expression match de l’encart 6-5 qui effectue une correspondance sur une valeur Option<i32> dans la variable x :

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Les motifs dans cette expression match sont None et Some(i) à gauche de chaque flèche.

Une exigence des expressions match est qu’elles doivent être exhaustives, c’est-à-dire que toutes les possibilités pour la valeur dans l’expression match doivent être couvertes. Un moyen de s’assurer que vous avez couvert toutes les possibilités est d’avoir un motif attrape-tout pour la dernière branche : par exemple, un nom de variable qui correspond à n’importe quelle valeur ne peut jamais échouer et couvre donc tous les cas restants.

Le motif particulier _ correspond à n’importe quoi, mais il ne se lie jamais à une variable, c’est pourquoi il est souvent utilisé dans la dernière branche de match. Le motif _ peut être utile lorsque vous souhaitez ignorer toute valeur non spécifiée, par exemple. Nous couvrirons le motif _ plus en détail dans la section [“Ignorer des valeurs dans un motif”][ignoring-values-in-a-pattern] plus loin dans ce chapitre.

Les instructions let

Avant ce chapitre, nous n’avions discuté explicitement de l’utilisation des motifs qu’avec match et if let, mais en réalité, nous avons utilisé des motifs à d’autres endroits également, notamment dans les instructions let. Par exemple, considérez cette assignation simple de variable avec let :

#![allow(unused)]
fn main() {
let x = 5;
}

Chaque fois que vous avez utilisé une instruction let comme celle-ci, vous utilisiez des motifs, même si vous ne vous en êtes peut-être pas rendu compte ! Plus formellement, une instruction let ressemble à ceci :

let PATTERN = EXPRESSION;

Dans les instructions comme let x = 5; avec un nom de variable à l’emplacement du MOTIF, le nom de variable n’est qu’une forme particulièrement simple de motif. Rust compare l’expression au motif et assigne tous les noms qu’il trouve. Ainsi, dans l’exemple let x = 5;, x est un motif qui signifie “lier ce qui correspond ici à la variable x.” Comme le nom x constitue l’intégralité du motif, ce motif signifie effectivement “lier tout à la variable x, quelle que soit la valeur.”

Pour voir l’aspect de correspondance de motifs de let plus clairement, considérez l’encart 19-1, qui utilise un motif avec let pour déstructurer un tuple.

fn main() {
    let (x, y, z) = (1, 2, 3);
}
Listing 19-1: Using a pattern to destructure a tuple and create three variables at once

Ici, nous faisons correspondre un tuple à un motif. Rust compare la valeur (1, 2, 3) au motif (x, y, z) et constate que la valeur correspond au motif – c’est-à-dire qu’il voit que le nombre d’éléments est le même dans les deux – donc Rust lie 1 à x, 2 à y et 3 à z. Vous pouvez considérer ce motif de tuple comme l’imbrication de trois motifs de variables individuels.

Si le nombre d’éléments dans le motif ne correspond pas au nombre d’éléments dans le tuple, le type global ne correspondra pas et nous obtiendrons une erreur du compilateur. Par exemple, l’encart 19-2 montre une tentative de déstructurer un tuple de trois éléments en deux variables, ce qui ne fonctionnera pas.

fn main() {
    let (x, y) = (1, 2, 3);
}
Listing 19-2: Incorrectly constructing a pattern whose variables don’t match the number of elements in the tuple

Tenter de compiler ce code produit cette erreur de type : console {{#include ../listings/ch19-patterns-and-matching/listing-19-02/output.txt}}

$ 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` (bin "patterns") due to 1 previous error

Pour corriger l’erreur, nous pourrions ignorer une où plusieurs valeurs du tuple en utilisant _ ou .., comme vous le verrez dans la section [“Ignorer des valeurs dans un motif”][ignoring-values-in-a-pattern]. Si le problème est que nous avons trop de variables dans le motif, la solution est de faire correspondre les types en supprimant des variables afin que le nombre de variables soit égal au nombre d’éléments dans le tuple.

Les expressions conditionnelles if let

Au chapitre 6, nous avons vu comment utiliser les expressions if let principalement comme une manière plus concise d’écrire l’équivalent d’un match qui ne correspond qu’à un seul cas. Optionnellement, if let peut avoir un else correspondant contenant du code à exécuter si le motif du if let ne correspond pas.

L’encart 19-3 montre qu’il est également possible de combiner des expressions if let, else if et else if let. Cela nous donne plus de flexibilité qu’une expression match dans laquelle nous ne pouvons exprimer qu’une seule valeur à comparer avec les motifs. De plus, Rust n’exige pas que les conditions d’une série de branches if let, else if et else if let soient liées entre elles.

Le code de l’encart 19-3 détermine quelle couleur donner à votre arrière-plan en fonction d’une série de vérifications de plusieurs conditions. Pour cet exemple, nous avons créé des variables avec des valeurs codées en dur qu’un vrai programme pourrait recevoir en entrée de l’utilisateur.

Filename: src/main.rs
fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {color}, as the background");
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}
Listing 19-3: Mixing if let, else if, else if let, and else

Si l’utilisateur spécifie une couleur préférée, cette couleur est utilisée comme arrière-plan. Si aucune couleur préférée n’est spécifiée et que nous sommes mardi, la couleur d’arrière-plan est le vert. Sinon, si l’utilisateur spécifie son âge sous forme de chaîne de caractères et que nous pouvons l’analyser avec succès comme un nombre, la couleur est soit le violet soit l’orange selon la valeur du nombre. Si aucune de ces conditions ne s’applique, la couleur d’arrière-plan est le bleu.

Cette structure conditionnelle nous permet de prendre en charge des exigences complexes. Avec les valeurs codées en dur que nous avons ici, cet exemple affichera Using purple as the background color.

Vous pouvez voir que if let peut aussi introduire de nouvelles variables qui masquent les variables existantes de la même manière que les branches de match : la ligne if let Ok(age) = age introduit une nouvelle variable age qui contient la valeur à l’intérieur de la variante Ok, masquant la variable age existante. 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 en if let Ok(age) = age && age > 30. Le nouveau age que nous voulons comparer à 30 n’est pas valide tant que la nouvelle portée ne commence pas avec l’accolade ouvrante.

L’inconvénient d’utiliser des expressions if let est que le compilateur ne vérifie pas l’exhaustivité, alors qu’il le fait avec les expressions match. Si nous omettions le dernier bloc else et manquions ainsi la gestion de certains cas, le compilateur ne nous alerterait pas du possible bogue logique.

Les boucles conditionnelles while let

De construction similaire à if let, la boucle conditionnelle while let permet à une boucle while de s’exécuter tant qu’un motif continue de correspondre. Dans l’encart 19-4, nous montrons une boucle while let qui attend des messages envoyés entre des tâches, mais dans ce cas en vérifiant un Result au lieu d’un Option.

fn main() {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        for val in [1, 2, 3] {
            tx.send(val).unwrap();
        }
    });

    while let Ok(value) = rx.recv() {
        println!("{value}");
    }
}
Listing 19-4: Using a while let loop to print values for as long as rx.recv() returns Ok

Cet exemple affiche 1, 2, puis 3. La méthode recv prend le premier message du côté récepteur du canal et retourné un Ok(value). Lorsque nous avons vu recv pour la première fois au chapitre 16, nous avions déballé l’erreur directement, ou nous avions interagi avec lui comme un itérateur en utilisant une boucle for. Comme le montre l’encart 19-4, cependant, nous pouvons aussi utiliser while let, car la méthode recv retourné un Ok chaque fois qu’un message arrive, tant que l’émetteur existe, puis produit un Err une fois que le côté émetteur se déconnecte.

Les boucles for

Dans une boucle for, la valeur qui suit directement le mot-clé for est un motif. Par exemple, dans for x in y, le x est le motif. L’encart 19-5 montre comment utiliser un motif dans une boucle for pour déstructurer, ou décomposer, un tuple dans le cadre de la boucle for.

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}
Listing 19-5: Using a pattern in a for loop to destructure a tuple

Le code de l’encart 19-5 affichera ce qui suit : console {{#include ../listings/ch19-patterns-and-matching/listing-19-05/output.txt}}

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2

Nous adaptons un itérateur en utilisant la méthode enumerate afin qu’il produise une valeur et l’indice de cette valeur, placés dans un tuple. La première valeur produite est le tuple (0, 'a'). Lorsque cette valeur est mise en correspondance avec le motif (index, value), index vaudra 0 et value vaudra 'a', affichant la première ligne de la sortie.

Les paramètres de fonction

Les paramètres de fonction peuvent également être des motifs. Le code de l’encart 19-6, qui déclare une fonction nommée foo prenant un paramètre nommé x de type i32, devrait désormais vous sembler familier.

fn foo(x: i32) {
    // code goes here
}

fn main() {}
Listing 19-6: A function signature using patterns in the parameters

La partie x est un motif ! Comme nous l’avons fait avec let, nous pourrions faire correspondre un tuple dans les arguments d’une fonction au motif. L’encart 19-7 décompose les valeurs d’un tuple lorsque nous le passons à une fonction.

Filename: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}
Listing 19-7: A function with parameters that destructure a tuple

Ce code affiche Current location: (3, 5). Les valeurs &(3, 5) correspondent au motif &(x, y), donc x vaut 3 et y vaut 5.

Nous pouvons également utiliser des motifs dans les listes de paramètres des fermetures de la même manière que dans les listes de paramètres des fonctions, car les fermetures sont similaires aux fonctions, comme nous l’avons vu au chapitre 13.

À ce stade, vous avez vu plusieurs manières d’utiliser les motifs, mais les motifs ne fonctionnent pas de la même façon à chaque endroit où nous pouvons les utiliser. À certains endroits, les motifs doivent être irréfutables ; dans d’autres circonstances, ils peuvent être réfutables. Nous allons discuter de ces deux concepts maintenant.