Confutabilità: Quando un Pattern Potrebbe non Corrispondere
I pattern si presentano in due forme: confutabili e inconfutabili. I pattern che corrispondono
per qualsiasi possibile valore passato sono inconfutabili. Un esempio sarebbe x
nell’
istruzione let x = 5;
perché x
corrisponde a qualsiasi cosa e quindi non può
non corrispondere. I pattern che possono non corrispondere per un possibile valore sono
confutabili. Un esempio sarebbe Some(x)
nell’espressione if let Some(x) = a_value
perché se il valore nella variabile a_value
è None
anziché
Some
, il pattern Some(x)
non corrisponderà.
I parametri di funzione, le istruzioni let
e i cicli for
possono accettare solo pattern
inconfutabili perché il programma non può fare nulla di significativo quando i valori
non corrispondono. Le espressioni if let
e while let
e l’istruzione
let...else
accettano pattern confutabili e inconfutabili, ma il
compilatore mette in guardia contro i pattern inconfutabili perché, per definizione, sono
pensati per gestire possibili fallimenti: la funzionalità di una condizione risiede
nella sua capacità di comportarsi in modo diverso a seconda del successo o del fallimento.
In generale, non ci si dovrebbe preoccupare della distinzione tra pattern confutabili e inconfutabili; tuttavia, è necessario avere familiarità con il concetto di confutabilità in modo da poter rispondere quando lo si vede in un messaggio di errore. In questi casi, sarà necessario modificare il pattern o il costrutto con cui si sta utilizzando il pattern, a seconda del comportamento previsto per il codice.
Esaminiamo un esempio di cosa succede quando proviamo a utilizzare un pattern confutabile
dove Rust richiede un pattern inconfutabile e viceversa. Il Listato 19-8 mostra un’istruzione
let
, ma per il pattern abbiamo specificato Some(x)
, un pattern
confutabile. Come ci si potrebbe aspettare, questo codice non verrà compilato.
fn main() {
let some_option_value: Option<i32> = None;
let Some(x) = some_option_value;
}
let
Se some_option_value
fosse un valore None
, non corrisponderebbe al pattern
Some(x)
, il che significa che il pattern è confutabile. Tuttavia, l’istruzione let
può
accettare solo un pattern inconfutabile perché non c’è nulla di valido che il codice possa
fare con un valore None
. In fase di compilazione, Rust si lamenterà del fatto che abbiamo provato a
utilizzare un pattern confutabile laddove è richiesto un pattern inconfutabile:
$ 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
Poiché non abbiamo coperto (e non potevamo coprire!) ogni valore valido con il
pattern Some(x)
, Rust genera giustamente un errore di compilazione.
Se abbiamo un pattern confutabile laddove è necessario un pattern inconfutabile, possiamo
correggerlo modificando il codice che utilizza il pattern: invece di usare let
, possiamo
usare let else
. Quindi, se il pattern non corrisponde, il codice salterà semplicemente
il codice tra parentesi graffe, consentendogli di continuare validamente. Il Listato
19-9 mostra come correggere il codice nel Listato 19-8.
fn main() { let some_option_value: Option<i32> = None; let Some(x) = some_option_value else { return; }; }
let...else
e un blocco con pattern confutabili invece di let
Abbiamo dato al codice una via d’uscita! Questo codice è perfettamente valido, anche se significa che
non possiamo usare un pattern inconfutabile senza ricevere un avviso. Se diamo a let...else
un pattern che corrisponderà sempre, come x
, come mostrato nel Listato
19-10, il compilatore genererà un avviso.
fn main() { let x = 5 else { return; }; }
let...else
Rust lamenta che non ha senso utilizzare let...else
con un
pattern inconfutabile:
$ 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`
Per questo motivo, i rami di corrispondenza devono utilizzare pattern inconfutabili, ad eccezione dell’ultimo
ramo, che dovrebbe corrispondere a tutti i valori rimanenti con un pattern inconfutabile. Rust
ci consente di utilizzare un pattern inconfutabile in un match
con un solo ramo, ma
questa sintassi non è particolarmente utile e potrebbe essere sostituita con una più semplice
istruzione let
.
Ora che sappiamo dove usare i pattern e la differenza tra pattern confutabili e irrefutabili, esaminiamo tutta la sintassi che possiamo usare per creare pattern.