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
matchsobrecmd, el brazoComando::Mover { x, y } if x == yutiliza un Guard. Sixyyson iguales, se ejecuta la primera línea; si no, pasa al siguiente brazo que capturaxeysin la restricción delif. El brazoComando::Ajustar(n) if n < 0también usa un guard para capturar el enteronsolo si es negativo. - En el segundo
matchsobrevalor, se utiliza el at-bindingn @ 1..=10. Esto permite que la variablencontenga el valor devalorsi este se encuentra en el rango de 1 a 10, permitiendo usarnen el mensaje deprintln!. - El macro
matches!(opcion, Some(n) if n > 0)se emplea dentro del métodofilter. Aquí,matches!evalúa cada elemento del vectorlista_opciones. Si el elemento es unSomey su contenidoncumple la condición del guardn > 0, el macro retornatruey el elemento se mantiene en el iterador. - La variable
es_positivorecibe el resultado booleano dematches!(Some(5), Some(n) if n > 0), demostrando cómo el macro puede integrar tanto el patrón de la varianteSomecomo la lógica de un guard.
N° 43