Refutabilidad: Si un Pattern Puede Fallar al Hacer Match

Los patterns se dividen en dos formas: refutables e irrefutables. Los patterns que coinciden con cualquier valor posible son irrefutables. Un ejemplo sería x en la declaración let x = 5; porque x coincide con cualquier cosa y, por lo tanto, no puede fallar al hacer match. Los patterns que pueden fallar al hacer match para algunos valores posibles son refutables. Un ejemplo sería Some(x) en la expresión if let Some(x) = a_value porque si el valor en la variable a_value es None en lugar de Some, el pattern Some(x) no coincidirá.

Los parámetros de funciones, las declaraciones let y los bucles for solo pueden aceptar patterns irrefutables, porque el programa no puede hacer nada significativo cuando los valores no coinciden. Las expresiones if let y while let aceptan patterns refutables e irrefutables, pero el compilador advierte contra los patterns irrefutables porque, por definición, están destinados a manejar posibles fallas: la funcionalidad de una condicional está en su capacidad de realizar de manera diferente dependiendo del éxito o el fracaso.

En general, no debería preocuparse por la distinción entre patterns refutables e irrefutables; sin embargo, debe estar familiarizado con el concepto de refutabilidad para poder responder cuando lo vea en un mensaje de error. En esos casos, deberá cambiar el pattern o la construcción que está utilizando el pattern, según el comportamiento previsto del código.

Veamos un ejemplo de lo que sucede cuando intentamos usar un pattern refutable donde Rust requiere un pattern irrefutable y viceversa. El Listado 18-8 muestra una declaración let, pero para el pattern hemos especificado Some(x), un pattern refutable. Como puede imaginar, este código no se compilará.

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

Listing 18-8: Intentando utilizar un pattern refutable con let

Si some_option_value fuera un valor None, no coincidiría con el pattern Some(x), lo que significa que el pattern es refutable. Sin embargo, la declaración let solo puede aceptar un pattern irrefutable porque no hay nada válido que el código pueda hacer con un valor None. En tiempo de compilación, Rust se quejará de que hemos intentado usar un pattern refutable donde se requiere un pattern irrefutable:

$ 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/ch18-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

Debido a que no hemos cubierto (¡y no pudimos cubrir!) Cada valor válido con el pattern Some(x), Rust produce un error del compilador.

Si tenemos un pattern refutable donde se necesita un patrón irrefutable, podemos solucionarlo cambiando el código que utiliza el patrón: en lugar de usar let, podemos usar if let. Entonces, si el pattern no coincide, el código simplemente omitirá el código entre llaves, dándole una forma de continuar válidamente. El Listado 18-9 muestra cómo solucionar el código del Listado 18-8.

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

Listing 18-9: Usando if let y un bloque con patterns refutables en lugar de let

¡Le hemos dado una solución al código! Este código es perfectamente válido ahora. Sin embargo, significa que no podemos usar un pattern irrefutable sin recibir un error. Si le damos a if let un pattern que siempre coincidirá, como x, como se muestra en el Listado 18-10, el compilador dará una advertencia.

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

Listing 18-10: Intentando usar un pattern irrefutable con if let

Rust se queja de que no tiene sentido usar if let con un pattern irrefutable:

$ 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` profile [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/patterns`
5

Por esta razón, las opciones del match deben usar patterns refutables, excepto por la última opción, que debe coincidir con cualquier valor restante con un pattern irrefutable. Rust nos permite usar un pattern irrefutable en un match con solo un brazo, pero esta sintaxis no es particularmente útil y podría reemplazarse con una declaración let más simple.

Ahora que sabes dónde usar patterns y la diferencia entre patterns refutables e irrefutables, cubramos toda la sintaxis que podemos usar para crear patterns.