La 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 18-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;
}

Encart 18-8 : tentative d'utilisation d'un motif réfutable avec 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. A 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: `None` not covered
   --> 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/ch18-02-refutability.html
    = note: the matched value is of type `Option<i32>`
help: you might want to use `if let` to ignore the variant that isn't matched
    |
3   |     if let Some(x) = une_option_quelconque { /* */ }
    |

For more information about this error, try `rustc --explain E0005`.
error: could not compile `patterns` due to 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.

Pour corriger le problème lorsque nous avons un motif réfutable là où un motif irréfutable est nécessaire, nous pouvons modifier le code qui utilise ce motif : au lieu d'utiliser let, nous pouvons utiliser if let. Dans ce cas, si le motif ne correspond pas, le programme va simplement sauter le code entre les accolades, ce qui lui permet de poursuivre son exécution sans rencontrer d'erreur. L'encart 18-9 montre comment corriger le code de l'encart 18-8.

fn main() {
    let une_option_quelconque: Option<i32> = None;
    if let Some(x) = une_option_quelconque {
        println!("{}", x);
    }
}

Encart 18-9 : utilisation de if let et d'un bloc avec un motif réfutable plutôt qu'un 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 une erreur. Si nous donnons au if let un motif qui correspond toujours, tel que x, comme montré dans l'encart 18-10, le compilateur va lever un avertissement.

fn main() {
    if let x = 5 {
        println!("{}", x);
    };
}

Encart 18-10 : tentative d'utiliser un motif irréfutable avec if let

Rust explique que cela ne fait aucun sens d'utiliser if let avec un motif irréfutable :

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
 --> src/main.rs:2:8
  |
2 |     if let x = 5 {
  |        ^^^^^^^^^
  |
  = note: `#[warn(irrefutable_let_patterns)]` on by default
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`

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

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.