match en Rust: pattern matching exhaustivo y potente

match es la expresión de control de flujo más potente de Rust. A diferencia del switch de C o Java, Rust exige que cubras todos los casos posibles. Si te olvidas de uno, el programa no compila. Esa propiedad de exhaustividad elimina bugs enteros antes de que lleguen a producción.

Sintaxis básica

let numero = 3;

let descripcion = match numero {
    1 => "uno",
    2 => "dos",
    3 => "tres",
    _ => "otro",  // _ captura cualquier valor no cubierto
};

println!("{}", descripcion);

match es una expresión: devuelve el valor del brazo que coincide. Cada brazo tiene la forma patrón => expresión.

Exhaustividad obligatoria

enum Color { Rojo, Verde, Azul }

fn describir(c: Color) -> &'static str {
    match c {
        Color::Rojo  => "rojo",
        Color::Verde => "verde",
        // ERROR: Azul no está cubierto
    }
}
error[E0004]: non-exhaustive patterns: `Color::Azul` not covered
  --> src/main.rs:5:11
   |
5  |     match c {
   |           ^ pattern `Color::Azul` not covered

Cuando añades una nueva variante a un enum, todos los match del código de producción fallan al compilar hasta que los actualizas. Es una refactorización asistida por el compilador.

Múltiples patrones con |

let x = 5;
match x {
    1 | 2        => println!("uno o dos"),
    3 | 4 | 5    => println!("tres, cuatro o cinco"),
    _            => println!("otro"),
}

Rangos con ..=

let nota = 7;
match nota {
    0..=4  => println!("Suspenso"),
    5..=6  => println!("Aprobado"),
    7..=8  => println!("Notable"),
    9..=10 => println!("Sobresaliente"),
    _      => println!("Nota inválida"),
}

Enums con datos: desestructuración

enum Mensaje {
    Mover { x: i32, y: i32 },
    Escribir(String),
    CambiarColor(u8, u8, u8),
    Salir,
}

fn procesar(m: Mensaje) {
    match m {
        Mensaje::Mover { x, y }        => println!("Mover ({}, {})", x, y),
        Mensaje::Escribir(texto)       => println!("Texto: {}", texto),
        Mensaje::CambiarColor(r, g, b) => println!("Color #{:02X}{:02X}{:02X}", r, g, b),
        Mensaje::Salir                 => println!("Salir"),
    }
}

Guards con if

Un match guard añade una condición booleana adicional al patrón:

let par = (3, -2);
match par {
    (x, y) if x > 0 && y > 0 => println!("Cuadrante I"),
    (x, y) if x < 0 && y > 0 => println!("Cuadrante II"),
    (x, _) if x == 0           => println!("En el eje Y"),
    _                          => println!("Otro"),
}

Bindear el valor con @

let numero = 7;
match numero {
    n @ 1..=9  => println!("Un dígito: {}", n),
    n @ 10..=99 => println!("Dos dígitos: {}", n),
    n           => println!("Tres o más: {}", n),
}

@ captura el valor que coincide con el patrón para usarlo en el brazo.

match con Option y Result

fn leer_numero(s: &str) -> Option<i32> {
    s.parse().ok()
}

fn main() {
    match leer_numero("42") {
        Some(n) if n > 0 => println!("Positivo: {}", n),
        Some(n)           => println!("No positivo: {}", n),
        None              => println!("No es un número"),
    }
}

Resumen

  • match es exhaustivo: cubrir todos los casos es obligatorio.
  • Soporta literales, rangos, enums, tuplas, structs y desestructuración.
  • Múltiples patrones con |, condiciones adicionales con guards if.
  • Captura valores con @.
  • Es una expresión: puede usarse en el lado derecho de una asignación.

El siguiente artículo cubre if let y while let: cómo usar pattern matching cuando solo te importa un caso y no necesitas la exhaustividad de un match completo.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP