Los patrones son bastante comunes en Rust. Los usamos en enlaces a variable, sentencias match, y otros casos. Embarquemonos en un tour torbellino por todas las cosas que los patrones son capaces de hacer!
Un repaso rápido: puedes probar patrones contra literales directamente, y _ actúa como un caso cualquiera:
let x = 1; match x { 1 => println!("uno"), 2 => println!("dos"), 3 => println!("tres"), _ => println!("cualquiera"), }
Imprime uno.
Puedes probar multiples patrones con |:
let x = 1; match x { 1 | 2 => println!("uno o dos"), 3 => println!("tres"), _ => println!("cualquiera"), }
Lo anterior imprime uno o dos.
Si posees un tipo de datos compuesto, como un struct, puedes destructurarlo dentro de un patron:
struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { x, y } => println!("({},{})", x, y), }
Puedes usar : para darle un nombre diferente a un valor.
struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { x: x1, y: y1 } => println!("({},{})", x1, y1), }
Si solo nos importan algunos valores, no tenemos que darle nombres a todos:
fn main() { struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { x, .. } => println!("x es {}", x), } }struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { x, .. } => println!("x es {}", x), }
Esto imprime x es 0.
Puedes hacer este tipo de pruebas en cualquier miembro, no solo el primero:
fn main() { struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { y, .. } => println!("y es {}", y), } }struct Punto { x: i32, y: i32, } let origen = Punto { x: 0, y: 0 }; match origen { Punto { y, .. } => println!("y es {}", y), }
Lo anterior imprime y es 0.
Este comportamiento de ‘destructuracion’ funciona en cualquier tipo de datos compuesto, como tuplas o enums.
Puedes usar _ en un patron para ignorar tanto el tipo como el valor.
Por ejemplo, he aquí un match contra un Result<T, E>:
match algun_valor { Ok(valor) => println!("valor obtenido: {}", valor), Err(_) => println!("ha ocurrido un error"), }
En el primer brazo, enlazamos el valor dentro de la variante Ok a la variable valor. Pero en el brazo Err usamos _ para ignorar el error especifico, y solo imprimir un mensaje de error general.
_ es valido en cualquier patron que cree un enlace a variable. También puede ser util para ignorar porciones de una estructura mas grande:
fn coordenada() -> (i32, i32, i32) { // generar y retornar algún tipo de tupla de tres elementos } let (x, _, z) = coordenada();
Aquí, asociamos ambos el primer y ultimo elemento de la tupla a x y z respectivamente, ignorando el elemento de la mitad.
Similarmente, puedes usar .. en un patrón para ignorar multiples valores.
enum TuplaOpcional { Valor(i32, i32, i32), Faltante, } let x = TuplaOpcional::Valor(5, -2, 3); match x { TuplaOpcional::Valor(..) => println!("Tupla obtenida!"), TuplaOpcional::Faltante => println!("Sin suerte."), }
Esto imprime Tupla obtenida!.
Si deseas obtener una referencia, debes usar la palabra reservada ref:
let x = 5; match x { ref r => println!("Referencia a {} obtenida", r), }
Imprime Referencia a 5 obtenida.
Acá, la r dentro del match posee el tipo &i32. En otras palabras la palabra reservada ref crea una referencia, para ser usada dentro del patrón. Si necesitas una referencia mutable ref mut funcionara de la misma manera:
let mut x = 5; match x { ref mut rm => println!("Referencia mutable a {} obtenida", rm), }
Puedes probar un rango de valors con ...:
let x = 1; match x { 1 ... 5 => println!("uno al cinco"), _ => println!("cualquier cosa"), }
Esto imprime uno al cinco.
Los rangos son usados mayormente con enteros y charss:
let x = '💅'; match x { 'a' ... 'j' => println!("letra temprana"), 'k' ... 'z' => println!("letra tardia"), _ => println!("algo mas"), }
This prints algo mas.
Puedes asociar valores a nombres con @:
let x = 1; match x { e @ 1 ... 5 => println!("valor de rango {} obtenido", e), _ => println!("lo que sea"), }
This prints valor de rango 1 obtenido. Lo anterior es util cuando desees hacer un match complicado a una parte de una estructura de datos:
#[derive(Debug)] struct Persona { nombre: Option<String>, } let nombre = "Steve".to_string(); let mut x: Option<Persona> = Some(Persona { nombre: Some(nombre) }); match x { Some(Persona { nombre: ref a @ Some(_), .. }) => println!("{:?}", a), _ => {} }
Dicho código imprime Some("Steve"): hemos asociado el nombre interno a a.
Si usas @ con |, necesitas asegurarte de que el nombre sea asociado en cada parte del patron:
let x = 5; match x { e @ 1 ... 5 | e @ 8 ... 10 => println!("valor de rango {} obtenido", e), _ => println!("lo que sea"), }
Puedes introducir guardias match (‘match guards’) con if:
enum EnteroOpcional { Valor(i32), Faltante, } let x = EnteroOpcional::Value(5); match x { EnteroOpcional::Valor(i) if i > 5 => println!("Entero mayor a cinco obtenido!"), EnteroOpcional::Valor(..) => println!("Entero obtenido!"), EnteroOpcional::Faltante => println!("Sin suerte."), }
Esto imprime Entero obtenido!".
Si estas usando if con multiples patrones, el if aplica a ambos lados:
let x = 4; let y = false; match x { 4 | 5 if y => println!("si"), _ => println!("no"), }
Lo anterior imprime no, debido a que el if aplica a el 4 | 5 completo, y no solo al 5. En otras palabras, la precedencia del if se comporta de la siguiente manera:
(4 | 5) if y => ...
y no así:
4 | (5 if y) => ...
Uff! Eso fue un montón de formas diferentes para probar cosas, y todas pueden ser mezcladas y probadas, dependiendo de los que estés haciendo:
fn main() { match x { Foo { x: Some(ref nombre), y: None } => ... } }match x { Foo { x: Some(ref nombre), y: None } => ... }
Los patrones son muy poderosos. Haz buen uso de ellos.