% Patrones
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
.
Multiples patrones
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
.
Destructuracion
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:
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:
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.
Ignorando enlaces a variables
Puedes usar _
en un patron para ignorar tanto el tipo como el valor.
Por ejemplo, he aquí un match
contra un Result<T, E>
:
# let algun_valor: Result<i32, &'static str> = Err("Hubo un error");
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
# (1, 2, 3)
}
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!
.
ref y ref mut
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),
}
Rangos
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 chars
s:
let x = '💅';
match x {
'a' ... 'j' => println!("letra temprana"),
'k' ... 'z' => println!("letra tardia"),
_ => println!("algo mas"),
}
This prints algo mas
.
Enlaces a variable
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"),
}
Guardias
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) => ...
Mezcla y Match
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:
match x {
Foo { x: Some(ref nombre), y: None } => ...
}
Los patrones son muy poderosos. Haz buen uso de ellos.