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;
}
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); } }
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); }; }
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.