Refutabilitatea: când un pattern ar putea eșua la potrivire

Există două tipuri de pattern-uri: refutabile și irefutabile. Pattern-urile care se vor potrivi cu orice valoare posibilă sunt irefutabile. De exemplu, x din let x = 5; este irefutabil, deoarece x poate reprezenta orice valoare și astfel nu poate să nu se potrivească. Pattern-urile care ar putea să nu se potrivească pentru anumite valori sunt refutabile. Un astfel de exemplu este Some(x) din if let Some(x) = a_value, unde dacă a_value este None și nu Some, pattern-ul Some(x) va eșua să se potrivească.

Parametrii funcțiilor, instrucțiunile let și buclele for necesită pattern-uri irefutabile, deoarece programul nu are cum să procedeze în mod semnificativ atunci când valorile nu corespund. Expresiile if let și while let permit utilizarea ambelor tipuri de pattern-uri, însă compilatorul va avertiza împotriva folosirii pattern-urilor irefutabile, deoarece acestea sunt proiectate să gestioneze posibilitatea unui eșec.

De regulă, nu ar trebui să ne îngrijorăm despre diferența dintre pattern-urile refutabile și irefutabile; totuși, este important să înțelegem conceptul de refutabilitate pentru a putea răspunde corect atunci când întâmpinăm acești termeni în mesajele de eroare ale compilatorului. În astfel de situații, va trebui să ajustăm fie pattern-ul, fie contextul în care este folosit, în funcție de comportamentul dorit în cod.

Să examinăm un exemplu care ilustrează ce se întâmplă când încercăm să folosim un pattern refutabil în locul unuia irefutabil și invers. Listarea 18-8 ne arată o instrucțiune let unde am utilizat Some(x), un pattern refutabil. După cum putem anticipa, codul acesta nu va fi compilat.

fn main() {
    let some_option_value: Option<i32> = None;
    let Some(x) = some_option_value;
}

<span class="caption">Listarea 18-8: Tentativa de a folosi un pattern refutabil cu `let`</span>

Dacă `some_option_value` ar fi `None`, nu va corespunde cu pattern-ul `Some(x)`, indicând că pattern-ul este refutabil. Totuși, `let` necesită un pattern irefutabil pentru că, altfel, nu poate fi efectuată nici o operațiune validă cu valoarea `None`. La momentul compilării, Rust va indica eroarea de a încerca să folosim un pattern refutabil unde este cerut unul irefutabil:

```console
$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0005]: refutable pattern in local binding: `None` not covered
 --> 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/ch18-02-refutability.html
note: `Option<i32>` defined here
 --> /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:518:1
  |
  = note: 
/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:522:5: not covered
  = note: the matched value is of type `Option<i32>`
help: you might want to use `if let` to ignore the variant that isn't matched
  |
3 |     let x = if let Some(x) = some_option_value { x } else { todo!() };
  |     ++++++++++                                 ++++++++++++++++++++++
help: alternatively, 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` due to previous error

Ne găsim în situația de a nu acoperi prin pattern-ul Some(x) toate valorile posibile, ceea ce conduce corect la o eroare de compilare în Rust.

Dacă avem un pattern refutabil unde se cere unul irefutabil, putem remedia situația modificând codul care îl folosește: în loc de let, putem opta pentru if let. Dacă pattern-ul nu se potrivește, codul va omite pur și simplu secțiunea din acolade, permițând continuarea executării în mod valid. Listarea 18-9 prezintă cum putem corecta codul din Listarea 18-8.

fn main() {
    let some_option_value: Option<i32> = None;
    if let Some(x) = some_option_value {
        println!("{}", x);
    }
}

Listarea 18-9: Utilizarea if let și a unui bloc cu pattern-uri refutabile în loc de let

Acum codul are o cale de ieșire! Această variantă este complet validă, chiar dacă înseamnă că nu putem folosi un pattern irefutabil fără a avea parte de o eroare. În cazul în care if let primește un pattern care va coincide mereu, cum ar fi x din Listarea 18-10, compilatorul va semnala un avertisment.

fn main() {
    if let x = 5 {
        println!("{}", x);
    };
}

Listarea 18-10: Încercarea de folosire a unui pattern irrefutabil cu if let

Rust sugerează că nu este logic să utilizăm if let cu un pattern care nu poate eșua:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
warning: irrefutable `if let` pattern
 --> src/main.rs:2:8
  |
2 |     if let x = 5 {
  |        ^^^^^^^^^
  |
  = note: this pattern will always match, so the `if let` is useless
  = help: consider replacing the `if let` with a `let`
  = note: `#[warn(irrefutable_let_patterns)]` on by default

warning: `patterns` (bin "patterns") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5

Din acest motiv, în instrucțiunea match, trebuie să folosim pattern-uri refutabile, cu excepția ultimului caz, care ar trebui să acopere orice valori rămase cu un pattern irefutabil. Deși Rust permite utilizarea unui pattern irefutabil într-un match cu un singur caz, această sintaxă nu este foarte practică și ar putea fi simplificată prin folosirea unei instrucțiuni let mai directe.

Înțelegând unde și cum să folosim pattern-urile, cât și distincția dintre cele refutabile și irefutabile, să explorăm acum toată sintaxa disponibilă pentru crearea pattern-urilor.