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