Para llevar el pattern matching al siguiente nivel, Rust proporciona tres herramientas que permiten refinar la precisión de los patrones: los Guards, los at-bindings (@) y el macro matches!.

Los Guards son expresiones condicionales (utilizando if) que se añaden al final de un patrón en un match o un if let. Su función no es cambiar la estructura del patrón, sino añadir una lógica booleana que debe cumplirse para que ese brazo de ejecución sea seleccionado. Es vital entender que el guard solo se evalúa si el patrón base coincide; si el if resulta falso, Rust simplemente continúa buscando en el siguiente brazo del match. Usar un guard de forma incorrecta (por ejemplo, con condiciones que no dependen de las variables capturadas) puede llevar a errores de lógica, pero su uso es estándar para filtrar valores específicos como “si x es mayor que 10”.

Los enlaces @ (at-bindings) resuelven un problema común: ¿cómo verificar que un valor pertenece a un rango o cumple un patrón y, al mismo tiempo, asignar ese valor a una variable? Sin @, tendrías que usar el patrón para validar y luego realizar una asignación manual o una comparación adicional. Con @, puedes decir: “si el valor cumple este patrón, asígnaselo a esta variable”. Esto es extremadamente útil en estructuras complejas o variantes de enum para extraer valores que cumplen una condición específica sin repetir código.

Finalmente, el macro matches! es una forma concisa de obtener un valor booleano basado en si una expresión coincide con un patrón. Es el sustituto ideal de la construcción if let cuando no necesitas ejecutar un bloque de código extenso, sino simplemente realizar una comprobación lógica. Es especialmente poderoso cuando se combina con guards o cuando se utiliza dentro de métodos de iteradores como filter(), permitiendo escribir código mucho más declarativo y limpio.

enum Comando {
    Mover { x: i32, y: i32 },
    Ajustar(i32),
    Salir,
}

fn main() {
    let cmd = Comando::Mover { x: 10, y: 10 };
    let valor = 5;

    // 1. Uso de Guards: Añaden condiciones lógicas al patrón
    match cmd {
        Comando::Mover { x, y } if x == y => println!("Movimiento diagonal en: {}", x),
        Comando::Mover { x, y } => println!("Movimiento en: ({}, {})", x, y),
        Comando::Ajustar(n) if n < 0 => println!("Ajuste negativo detectado: {}", n),
        _ => println!("Otro comando o Salir"),
    }

    // 2. Uso de @ bindings: Captura el valor y lo asigna simultáneamente
    match valor {
        n @ 1..=10 => println!("El valor {} está en el rango 1-10", n),
        n @ 11..=20 => println!("El valor {} está en el rango 11-20", n),
        _ => println!("Fuera de los rangos controlados"),
    }

    // 3. Uso de matches!: Devuelve un bool de forma rápida y concisa
    // Caso con guardia dentro del macro y en un iterador
    let lista_opciones = vec![Some(5), Some(-2), Some(12), None];

    // Comprobación directa con matches! y guard
    let es_positivo = matches!(Some(5), Some(n) if n > 0);
    println!("¿Es Some(5) un valor positivo? {}", es_positive);

    // Uso de matches! dentro de un filter() de un iterador
    let positivos = lista_opciones.into_iter().filter(|opcion| {
        matches!(opcion, Some(n) if n > 0)
    });

    println!("Valores positivos encontrados:");
    for p in positivos {
        println!("{:?}", p);
    }
}

Explicación del Código

  • En el primer match sobre cmd, el brazo Comando::Mover { x, y } if x == y utiliza un Guard. Si x y y son iguales, se ejecuta la primera línea; si no, pasa al siguiente brazo que captura x e y sin la restricción del if. El brazo Comando::Ajustar(n) if n < 0 también usa un guard para capturar el entero n solo si es negativo.
  • En el segundo match sobre valor, se utiliza el at-binding n @ 1..=10. Esto permite que la variable n contenga el valor de valor si este se encuentra en el rango de 1 a 10, permitiendo usar n en el mensaje de println!.
  • El macro matches!(opcion, Some(n) if n > 0) se emplea dentro del método filter. Aquí, matches! evalúa cada elemento del vector lista_opciones. Si el elemento es un Some y su contenido n cumple la condición del guard n > 0, el macro retorna true y el elemento se mantiene en el iterador.
  • La variable es_positivo recibe el resultado booleano de matches!(Some(5), Some(n) if n > 0), demostrando cómo el macro puede integrar tanto el patrón de la variante Some como la lógica de un guard.

43