Enums y Pattern Matching
Los enums de Rust permiten que cada variante lleve sus propios datos, y match te obliga a manejar cada una antes de que tu código compile.
En muchos lenguajes, un 'enum' es solo un entero disfrazado: Color { RED, GREEN, BLUE } se compila como 0, 1, 2, y todas las variantes tienen exactamente la misma forma — ninguna. Los enums de Rust son una idea distinta y mucho más poderosa: cada variante de un enum puede llevar sus propios datos, con su propia forma, y el compilador rastrea qué forma tienes en cada punto de tu programa. Un tipo Message puede tener una variante Quit sin nada asociado, una variante Move { x: i32, y: i32 } que se comporta como un struct incrustado, una variante Write(String) que envuelve un único valor, y una variante ChangeColor(i32, i32, i32) que envuelve tres — todas bajo un mismo tipo, y todas manejadas con la misma herramienta.
Definir un enum consiste en listar sus variantes dentro de enum Nombre { ... }, separadas por comas, y cada variante puede ser un nombre simple, una lista de tipos al estilo tupla, o una lista de campos con nombre al estilo struct — no estás limitado a un solo estilo en todo el enum. Esto es lo que convierte a un enum de Rust en un verdadero 'tipo suma': un valor Message es exactamente uno de Quit, Move, Write o ChangeColor en cualquier momento dado, nunca más de uno ni una mezcla parcial, y la memoria que Rust le reserva tiene el tamaño de la variante más grande. Compáralo con un struct, que contiene todos sus campos a la vez — los enums y los structs son las dos formas complementarias de combinar tipos: 'uno de estos' frente a 'todos estos.'
La herramienta construida para consumir un enum es match, y su característica definitoria es la exhaustividad: el compilador exige que cada rama cubra cada variante posible (y, para tipos como los enteros, cada valor posible) antes de permitir que tu código compile. Olvida una variante y obtienes un error de compilación — 'non-exhaustive patterns' — no una sorpresa en tiempo de ejecución tres meses después, cuando alguien agregue una quinta variante a Message y un punto de llamada nunca la maneje sin que nadie lo note. Esta es la razón principal por la que los enums de Rust superan a un switch estilo C sobre una constante entera: un switch puede caer al siguiente caso (fall through), puede ignorar un caso en silencio y no le da al compilador nada contra qué verificar, mientras que match convierte '¿manejé todos los casos?' de una pregunta de revisión de código en una garantía en tiempo de compilación.
Dentro de una rama de match, el patrón no solo comprueba qué variante tienes — también extrae los datos de ella en el mismo paso. Message::Move { x, y } => ... vincula x e y directamente desde la variante al estilo struct; Message::Write(text) => ... vincula text desde la variante al estilo tupla. Los patrones también pueden comparar valores simples, rangos (4..=9), varias opciones a la vez (1 | 2 | 3), y añadir una condición extra con un guard (n if n < 0); cuando de verdad no te importan los casos restantes, la rama comodín _ satisface la exhaustividad capturando todo lo que no coincidió antes. La regla que hay que interiorizar es el orden: las ramas se evalúan de arriba hacia abajo, y gana el primer patrón que coincide, así que un _ (o cualquier otro patrón demasiado amplio) colocado demasiado pronto te robará en silencio casos que querías manejar más abajo.
Escribir un match completo solo para manejar una variante interesante e ignorar el resto es lo bastante común como para que Rust te dé un atajo: if let Some(max) = config_max { ... } ejecuta el bloque solo cuando el patrón coincide, con un else opcional para todo lo demás, y sin necesidad de una rama _. Es exactamente equivalente a un match con una rama real y una rama _ => {}, solo que sin la ceremonia — recurre a if let cuando de verdad solo te importa un único caso, y vuelve a match en el momento en que necesites reaccionar de forma distinta ante más de uno.
La misma idea extendida a un bucle es while let: while let Some(top) = stack.pop() { ... } sigue ejecutando el cuerpo mientras el patrón siga coincidiendo, y se detiene en el instante en que deja de hacerlo — aquí, en el momento en que pop() devuelve None porque la pila está vacía. Es la forma idiomática de vaciar una colección o consumir un flujo de valores Option/Result de uno en uno sin escribir a mano un loop { match ... { break } }.
Juntos, los enums y match le dan a Rust una forma de modelar 'esto o aquello, y nada más' con una precisión que la mayoría de los lenguajes convencionales no puede verificar en tiempo de compilación — y esa precisión resulta esencial en la siguiente lección, donde Option y Result resultan no ser más que enums que ya has aprendido a manejar.
enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),}impl Message {fn call(&self) {match self {Message::Quit => println!("Quit: no data attached"),Message::Move { x, y } => println!("Move to ({}, {})", x, y),Message::Write(text) => println!("Write: {}", text),Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),}}}fn main() {let messages = vec![Message::Quit,Message::Move { x: 10, y: 20 },Message::Write(String::from("hello")),Message::ChangeColor(255, 0, 0),];for msg in &messages {msg.call();}}
An enum whose variants carry completely different shapes of data — nothing, named fields, a single value, or three values — matched exhaustively inside its own impl block.
fn describe_number(n: i32) -> &'static str {match n {0 => "zero",1 | 2 | 3 => "small",4..=9 => "medium",n if n < 0 => "negative",_ => "large",}}fn main() {for n in [-5, 0, 2, 7, 100] {println!("{} is {}", n, describe_number(n));}}
match on plain values: ranges, multiple values per arm, a guard condition, and a final wildcard arm that makes the match exhaustive.
fn main() {let config_max: Option<u8> = Some(3);// if let: handle just the case you care about, skip the boilerplate match.if let Some(max) = config_max {println!("Maximum is configured to be {}", max);} else {println!("No maximum configured");}// while let: keep matching and looping until the pattern no longer matches.let mut stack = vec![1, 2, 3, 4, 5];while let Some(top) = stack.pop() {println!("popped {}", top);}}
if let as a shortcut for a single interesting case, and while let to drain a Vec by repeatedly matching on pop()'s Option until it returns None.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. You write a match over an enum with 3 variants, handle only 2 of them explicitly, and add no `_` catch-all arm. What happens?