Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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;
}
Listato 19-8: Tentativo di utilizzare un pattern confutabile con let

Se 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;
    };
}
Listato 19-9: Usare 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 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;
    };
}
Listato 19-10: Tentativo di utilizzare un pattern inconfutabile con let...else

Rust 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.