En esta página
Enums y pattern matching
Enums: representar múltiples formas
Los enums (enumeraciones) en Rust son mucho más poderosos que en la mayoría de lenguajes. Cada variante puede llevar datos distintos, haciendo que los enums de Rust sean equivalentes a los "tipos suma" o "union types" de la teoría de tipos.
Enums básicos
#[derive(Debug)]
enum DiaSemana {
Lunes,
Martes,
Miércoles,
Jueves,
Viernes,
Sábado,
Domingo,
}
fn es_laboral(dia: &DiaSemana) -> bool {
matches!(dia, DiaSemana::Lunes | DiaSemana::Martes | DiaSemana::Miércoles
| DiaSemana::Jueves | DiaSemana::Viernes)
}
fn main() {
let hoy = DiaSemana::Miércoles;
println!("Hoy: {:?}", hoy);
println!("¿Es laboral? {}", es_laboral(&hoy));
}Enums con datos
La característica más poderosa: cada variante puede llevar distintos tipos de datos:
#[derive(Debug)]
enum Mensaje {
Salir, // Sin datos
Mover { x: i32, y: i32 }, // Struct anónimo
Texto(String), // Un String
Color(u8, u8, u8), // Tres u8 (RGB)
Adjunto { nombre: String, bytes: Vec<u8> }, // Struct con Vec
}
impl Mensaje {
fn procesar(&self) {
match self {
Mensaje::Salir => println!("Cerrando aplicación..."),
Mensaje::Mover { x, y } => println!("Moviendo a ({x}, {y})"),
Mensaje::Texto(t) => println!("Texto recibido: {t}"),
Mensaje::Color(r, g, b) => println!("Color: rgb({r}, {g}, {b})"),
Mensaje::Adjunto { nombre, bytes } => {
println!("Adjunto '{}' ({} bytes)", nombre, bytes.len())
}
}
}
}
fn main() {
let mensajes = vec![
Mensaje::Mover { x: 10, y: 20 },
Mensaje::Texto(String::from("¡Hola!")),
Mensaje::Color(255, 128, 0),
Mensaje::Adjunto {
nombre: String::from("foto.jpg"),
bytes: vec![0xFF, 0xD8, 0xFF],
},
Mensaje::Salir,
];
for m in &mensajes {
m.procesar();
}
}Option: adiós al null
Rust no tiene null. En su lugar, usa el enum Option<T> de la biblioteca estándar:
enum Option<T> {
Some(T), // Hay un valor
None, // No hay valor
}fn encontrar_indice(lista: &[i32], objetivo: i32) -> Option<usize> {
for (i, &n) in lista.iter().enumerate() {
if n == objetivo {
return Some(i);
}
}
None
}
fn dividir(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
fn main() {
let numeros = [3, 1, 4, 1, 5, 9, 2, 6];
match encontrar_indice(&numeros, 5) {
Some(i) => println!("Encontrado en índice {i}"),
None => println!("No encontrado"),
}
// Métodos convenientes de Option
let resultado = dividir(10.0, 3.0);
println!("Resultado: {:.4}", resultado.unwrap_or(0.0));
let nada = dividir(5.0, 0.0);
println!("División por cero: {}", nada.is_none());
// map: transformar el valor si existe
let doble = resultado.map(|r| r * 2.0);
println!("Doble: {:.4}", doble.unwrap_or(0.0));
// and_then: encadenar operaciones que pueden fallar
let cadena = Some("42");
let numero: Option<i32> = cadena.and_then(|s| s.parse().ok());
println!("Número: {:?}", numero);
}Result: manejo de errores
Result<T, E> es el otro enum fundamental de la biblioteca estándar:
enum Result<T, E> {
Ok(T), // Operación exitosa con valor T
Err(E), // Error de tipo E
}use std::num::ParseIntError;
fn parsear_numero(s: &str) -> Result<i32, ParseIntError> {
s.trim().parse::<i32>()
}
fn main() {
let casos = ["42", " -7 ", "hola", "999999999999"];
for caso in &casos {
match parsear_numero(caso) {
Ok(n) => println!("'{caso}' → {n}"),
Err(e) => println!("'{caso}' → Error: {e}"),
}
}
}match: el corazón del pattern matching
match es el operador más poderoso de Rust para trabajar con enums. Es exhaustivo: debe cubrir todos los casos posibles:
fn describir_numero(n: i32) -> &'static str {
match n {
i32::MIN..=-1 => "negativo",
0 => "cero",
1..=9 => "dígito",
10..=99 => "dos dígitos",
100..=999 => "tres dígitos",
_ => "grande",
}
}
fn clasificar_char(c: char) -> &'static str {
match c {
'a'..='z' | 'á' | 'é' | 'í' | 'ó' | 'ú' | 'ñ' => "letra minúscula",
'A'..='Z' | 'Á' | 'É' | 'Í' | 'Ó' | 'Ú' | 'Ñ' => "letra mayúscula",
'0'..='9' => "dígito",
' ' | '\t' | '\n' => "espacio",
_ => "símbolo",
}
}
fn main() {
for n in [-5, 0, 7, 42, 500, 10_000] {
println!("{n}: {}", describir_numero(n));
}
for c in ['a', 'Z', '5', ' ', '@', 'ñ'] {
println!("'{c}': {}", clasificar_char(c));
}
}Guards en match
Los guards permiten añadir condiciones adicionales a los brazos del match:
fn evaluar_temperatura(temp: f64) -> &'static str {
match temp {
t if t < -30.0 => "peligrosamente frío",
t if t < 0.0 => "bajo cero",
t if t < 15.0 => "frío",
t if t < 25.0 => "confortable",
t if t < 35.0 => "caluroso",
t if t < 40.0 => "muy caluroso",
_ => "peligrosamente caliente",
}
}
fn main() {
for temp in [-40.0, -5.0, 10.0, 22.0, 33.0, 38.0, 42.0] {
println!("{temp:.1}°C: {}", evaluar_temperatura(temp));
}
}if let y while let: pattern matching conciso
Cuando solo te interesa un caso del enum:
fn main() {
let configuracion: Option<u32> = Some(8080);
// Con match (verboso para un solo caso):
match configuracion {
Some(puerto) => println!("Puerto configurado: {puerto}"),
None => (), // No hacemos nada
}
// Con if let (más conciso):
if let Some(puerto) = configuracion {
println!("Puerto: {puerto}");
}
// if let con else:
if let Some(puerto) = configuracion {
println!("Puerto custom: {puerto}");
} else {
println!("Usando puerto por defecto: 3000");
}
// while let: iterar hasta que el patrón no coincida
let mut pila = vec![1, 2, 3, 4, 5];
while let Some(cima) = pila.pop() {
print!("{cima} ");
}
println!();
// Encadenando if let
let valor: Result<Option<i32>, String> = Ok(Some(42));
if let Ok(Some(n)) = valor {
println!("Valor anidado: {n}");
}
}Patrones anidados y destructuring
#[derive(Debug)]
struct Punto { x: i32, y: i32 }
fn main() {
let punto = Punto { x: 3, y: -5 };
// Destructuring en match
let descripcion = match punto {
Punto { x: 0, y: 0 } => "origen",
Punto { x, y: 0 } => "en el eje X",
Punto { x: 0, y } => "en el eje Y",
Punto { x, y } if x == y => "en la diagonal",
_ => "punto arbitrario",
};
println!("{descripcion}");
// Binding con @
let n = 15;
match n {
x @ 1..=10 => println!("{x} está entre 1 y 10"),
x @ 11..=20 => println!("{x} está entre 11 y 20"),
x => println!("{x} está fuera del rango"),
}
// Tuplas en match
let coordenadas = (1, -1);
match coordenadas {
(0, 0) => println!("Origen"),
(x, 0) | (0, x) => println!("En eje con {x}"),
(x, y) if x == -y => println!("Antidiagonal"),
(x, y) => println!("({x}, {y})"),
}
}Los enums y el pattern matching hacen que el código Rust sea extraordinariamente expresivo. La próxima lección cubre las colecciones de la biblioteca estándar — Vec, String y HashMap.
#[derive(Debug)]
enum Forma {
Circulo(f64),
Rectangulo { ancho: f64, alto: f64 },
Triangulo(f64, f64, f64),
}
impl Forma {
fn area(&self) -> f64 {
match self {
Forma::Circulo(r) => std::f64::consts::PI * r * r,
Forma::Rectangulo { ancho, alto } => ancho * alto,
Forma::Triangulo(a, b, c) => {
// Fórmula de Herón
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
fn nombre(&self) -> &str {
match self {
Forma::Circulo(_) => "Círculo",
Forma::Rectangulo { .. } => "Rectángulo",
Forma::Triangulo(..) => "Triángulo",
}
}
}
fn main() {
let formas: Vec<Forma> = vec![
Forma::Circulo(5.0),
Forma::Rectangulo { ancho: 4.0, alto: 6.0 },
Forma::Triangulo(3.0, 4.0, 5.0),
];
for forma in &formas {
println!("{}: área = {:.4}", forma.nombre(), forma.area());
}
}
Inicia sesión para guardar tu progreso