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

Réfutabilité : lorsqu’un motif peut échouer à correspondre

Les motifs se divisent en deux catégories : réfutables et irréfutables. Les motifs qui vont correspondre à n’importe quelle valeur qu’on lui passe sont irréfutables. Un exemple serait le x dans l’instruction let x = 5; car x correspond à tout ce qui est possible de sorte que la correspondance ne puisse pas échouer. Les motifs pour lesquels la correspondance peut échouer pour certains valeurs sont réfutables. Un exemple serait Some(x) dans l’expression if let Some(x) = une_valeur car si la valeur dans la variable une_valeur est None au lieu de Some, le motif Some(x) ne correspondra pas.

Les paramètres de fonctions, les instructions let et les boucles for ne peuvent accepter que des motifs irréfutables car le programme ne peut rien faire d’autre lorsque les valeurs ne correspondent pas. Les expressions if let et while let acceptent les motifs réfutables et irréfutables, mais dans le second cas, le compilateur affichera une mise en garde car, par définition, ces expressions sont destinées à gérer un problème éventuel : le but des conditions est de se comporter différemment en fonction de la réussite ou de l’échec.

De manière générale, vous ne devriez pas avoir à vous soucier des différences entre les motifs réfutables et irréfutables ; en revanche, vous devez vous familiariser avec le concept de réfutabilité afin que vous puissiez comprendre ce qui se passe lorsque vous le verrez apparaître dans un message d’erreur. Dans ce cas, vous allez avoir besoin de changer soit le motif, soit la construction avec laquelle vous l’utilisez, selon le comportement attendu du code.

Examinons un exemple de ce qu’il se passe lorsque nous essayons d’utiliser un motif réfutable lorsque Rust prévoit d’utiliser un motif irréfutable, et vice-versa. L’encart 19-8 montre une instruction let, mais comme le motif nous avons indiqué Some(x), un motif réfutable. Comme vous pouvez vous en douter, ce code ne va pas se compiler.

fn main() {
    let une_option_quelconque: Option<i32> = None;
    let Some(x) = une_option_quelconque;
}
Listing 19-8: Attempting to use a refutable pattern with let

Si une_option_quelconque était une valeur None, elle ne correspondrait pas au motif Some(x), ce qui signifie que le motif est réfutable. Cependant, l’instruction let ne peut accepter qu’un motif irréfutable car il n’existe pas d’instructions valides à exécuter avec une valeur None. À la compilation, Rust s’y opposera en expliquant que nous avons essayé d’utiliser un motif réfutable là où un motif irréfutable est nécessaire :

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding
 --> src/main.rs:3:9
  |
3 |     let Some(x) = une_option_quelconque;
  |         ^^^^^^^ pattern `None` not covered
  |
  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
  = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html
  = note: the matched value is of type `Option<i32>`
help: you might want to use `let...else` to handle the variant that isn't matched
  |
3 |     let Some(x) = une_option_quelconque else { todo!() };
  |                                         ++++++++++++++++

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error

Comme nous n’avons pas couvert (et nous ne pouvons pas le faire !) chaque valeur possible avec le motif Some(x), Rust génère une erreur de compilation, à juste titre.

Si nous avons un motif réfutable là où un motif irréfutable est nécessaire, nous pouvons le corriger en modifiant le code qui utilise ce motif : au lieu d’utiliser let, nous pouvons utiliser let...else. Dans ce cas, si le motif ne correspond pas, le code entre les accolades va gérer la valeur. L’encart 19-9 montre comment corriger le code de l’encart 19-8.

fn main() {
    let une_option_quelconque: Option<i32> = None;
    if let Some(x) = une_option_quelconque else {
        return;
    };
}
Listing 19-9: Using let...else and a block with refutable patterns instead of let

Nous avons donné au code une porte de sortie. Ce code est parfaitement valide, cependant il implique que nous ne pouvons pas utiliser un motif irréfutable sans provoquer un avertissement. Si nous donnons au let...else un motif qui correspond toujours, tel que x, comme montré dans l’encart 19-10, le compilateur va lever un avertissement.

fn main() {
    let x = 5 else {
        return;
    };
}
Listing 19-10: Attempting to use an irrefutable pattern with let...else

Rust explique que cela ne fait aucun sens d’utiliser let...else avec un motif irréfutable, car le else ne pourra jamais être atteint :

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: unreachable `else` clause
 --> src/main.rs:2:15
  |
2 |     let x = 5 else {
  |     --------- ^^^^
  |     |
  |     assigning to binding pattern will always succeed
  |
  = note: this pattern always matches, so the else clause is unreachable
  = note: `#[warn(irrefutable_let_patterns)]` on by default

warning: `patterns` (bin "patterns") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`

C’est pourquoi les branches de match doivent utiliser des motifs réfutables, sauf pour la dernière branche, qui devrait correspondre à n’importe quelle valeur grâce à un motif irréfutable. Rust nous permet d’utiliser un motif irréfutable dans un match ne possédant qu’une seule branche, mais cette syntaxe n’est pas particulièrement utile et devrait être remplacée par une instruction let plus simple.

Maintenant que vous savez où utiliser les motifs et que vous connaissez la différence entre les motifs réfutables et irréfutables, voyons toutes les syntaxes que nous pouvons utiliser pour créer des motifs.