La réfutabilité : quand un motif pourrait ne pas correspondre
Les motifs se présentent sous deux formes : réfutables et irréfutables. Les motifs qui correspondent à toute valeur possible sont irréfutables. Un exemple serait x dans l’instruction let x = 5; car x correspond à n’importe quoi et ne peut donc pas échouer. Les motifs qui peuvent échouer pour certaines valeurs possibles sont réfutables. Un exemple serait Some(x) dans l’expression if let Some(x) = a_value car si la valeur dans la variable a_value est None plutôt que Some, le motif Some(x) ne correspondra pas.
Les paramètres de fonction, les instructions let et les boucles for ne peuvent accepter que des motifs irréfutables car le programme ne peut rien faire de significatif lorsque les valeurs ne correspondent pas. Les expressions if let et while let et l’instruction let...else acceptent des motifs réfutables et irréfutables, mais le compilateur avertit contre les motifs irréfutables car, par définition, ils sont destinés à gérer un éventuel échec : la fonctionnalité d’une condition réside dans sa capacité à se comporter différemment selon le succès ou l’échec.
En général, vous ne devriez pas avoir à vous soucier de la distinction entre motifs réfutables et irréfutables ; cependant, vous devez être familier avec le concept de réfutabilité afin de pouvoir réagir lorsque vous le rencontrez dans un message d’erreur. Dans ces cas, vous devrez modifier soit le motif, soit la construction avec laquelle vous utilisez le motif, selon le comportement souhaité du code.
Regardons un exemple de ce qui se passe lorsque nous essayons d’utiliser un motif réfutable là où Rust exige un motif irréfutable et vice versa. L’encart 19-8 montre une instruction let, mais pour le motif, nous avons spécifié Some(x), un motif réfutable. Comme vous pouvez vous y attendre, ce code ne compilera pas.
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value;
}
letSi some_option_value é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 le code ne peut rien faire de valide avec une valeur None. Au moment de la compilation, Rust se plaindra que nous avons essayé d’utiliser un motif réfutable là où un motif irréfutable est requis : console {{#include ../listings/ch19-patterns-and-matching/listing-19-08/output.txt}}
$ 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) = some_option_value;
| ^^^^^^^ 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) = some_option_value 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 ne pouvions pas couvrir !) toutes les valeurs valides avec le motif Some(x), Rust produit à juste titre une erreur de compilation.
Si nous avons un motif réfutable là où un motif irréfutable est nécessaire, nous pouvons corriger cela en modifiant le code qui utilise le motif : au lieu d’utiliser let, nous pouvons utiliser let...else. Alors, si le motif ne correspond pas, le code entre les accolades gérera la valeur. L’encart 19-9 montre comment corriger le code de l’encart 19-8.
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value else {
return;
};
}
let...else and a block with refutable patterns instead of letNous avons donné au code une porte de sortie ! Ce code est parfaitement valide, bien que cela signifie que nous ne pouvons pas utiliser un motif irréfutable sans recevoir un avertissement. Si nous donnons à let...else un motif qui correspondra toujours, comme x, comme montré dans l’encart 19-10, le compilateur émettra un avertissement.
fn main() {
let x = 5 else {
return;
};
}
let...elseRust se plaint qu’il n’a pas de sens d’utiliser let...else avec un motif irréfutable : console {{#include ../listings/ch19-patterns-and-matching/listing-19-10/output.txt}}
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `let...else` pattern
--> src/main.rs:2:5
|
2 | let x = 5 else {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `else` clause is useless
= help: consider removing the `else` clause
= 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`
Pour cette raison, les branches de match doivent utiliser des motifs réfutables, sauf pour la dernière branche, qui doit correspondre à toutes les valeurs restantes avec un motif irréfutable. Rust nous permet d’utiliser un motif irréfutable dans un match avec une seule branche, mais cette syntaxe n’est pas particulièrement utile et pourrait être remplacée par une instruction let plus simple.
Maintenant que vous savez où utiliser les motifs et la différence entre les motifs réfutables et irréfutables, couvrons toute la syntaxe que nous pouvons utiliser pour créer des motifs.