Confutabilità: Quando un Pattern Potrebbe non Corrispondere
I pattern si presentano in due forme: confutabili e inconfutabili
(refutable/irrefutable). I pattern che corrispondono per qualsiasi possibile
valore passato sono inconfutabili. Un esempio sarebbe x nella dichiarazione
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) = un_valore perché se il valore nella variabile un_valore è None
anziché Some, il pattern Some(x) non corrisponderà.
I parametri di funzione, le dichiarazioni 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 la dichiarazione let...else accettano sia pattern confutabili
che 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 poterlo comprendere 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 una dichiarazione 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 un_valore_option: Option<i32> = None;
let Some(x) = un_valore_option;
}
letSe un_valore_option fosse un valore None, non corrisponderebbe al pattern
Some(x), il che significa che il pattern è confutabile. Tuttavia, la
dichiarazione 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 ci segnalerà il fatto che abbiamo provato a utilizzare un
pattern confutabile laddove è richiesto un pattern inconfutabile:
$ cargo run
Compiling patterns v0.1.0 (file:///progetti/patterns)
error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(x) = un_valore_option;
| ^^^^^^^ 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) = un_valore_option 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 un_valore_option: Option<i32> = None; let Some(x) = un_valore_option else { return; }; }
let...else e un blocco con pattern confutabili invece di letAbbiamo dato al codice una via d’uscita! Questo codice è perfettamente valido,
anche se significa che non potremmo usare un pattern inconfutabile senza
ricevere un avviso. Se diamo a let...else un pattern che corrisponderà
sempre, ed esempio x, come mostrato nel Listato 19-10, il compilatore genererà
un avviso.
fn main() { let x = 5 else { return; }; }
let...elseRust segnala che non ha senso utilizzare let...else con un pattern
inconfutabile:
$ cargo run
Compiling patterns v0.1.0 (file:///progetti/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 match 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 singolo ramo, ma questa sintassi non è
particolarmente utile e potrebbe essere sostituita con una più semplice
dichiarazione let.
Ora che sappiamo dove usare i pattern e la differenza tra pattern confutabili e inconfutabili, esaminiamo tutta la sintassi che possiamo usare per creare pattern.