Enums y Pattern Matching
Los enums (enumeraciones) en Rust son extremadamente poderosos y van mucho más allá de las enumeraciones simples de otros lenguajes. Permiten definir tipos que pueden ser una de varias variantes, y cada variante puede contener diferentes tipos y cantidades de datos.
Definiendo Enums
Enum Básico
#[derive( Debug )]
enum DireccionIP {
V4 ,
V6 ,
}
fn main () {
let casa = DireccionIP :: V4 ;
let oficina = DireccionIP :: V6 ;
println! ( "Casa: {:?}" , casa);
println! ( "Oficina: {:?}" , oficina);
}
Enums con Datos
#[derive( Debug )]
enum DireccionIP {
V4 ( u8 , u8 , u8 , u8 ),
V6 ( String ),
}
fn main () {
let casa = DireccionIP :: V4 ( 127 , 0 , 0 , 1 );
let oficina = DireccionIP :: V6 ( String :: from ( "::1" ));
println! ( "Casa: {:?}" , casa);
println! ( "Oficina: {:?}" , oficina);
}
Enums con Diferentes Tipos de Datos
#[derive( Debug )]
enum Mensaje {
Salir ,
Mover { x : i32 , y : i32 },
Escribir ( String ),
CambiarColor ( i32 , i32 , i32 ),
}
fn main () {
let mensajes = vec! [
Mensaje :: Salir ,
Mensaje :: Mover { x : 10 , y : 15 },
Mensaje :: Escribir ( String :: from ( "Hola mundo" )),
Mensaje :: CambiarColor ( 255 , 0 , 128 ),
];
for mensaje in mensajes {
procesar_mensaje (mensaje);
}
}
fn procesar_mensaje (msg : Mensaje ) {
match msg {
Mensaje :: Salir => println! ( "Saliendo del programa" ),
Mensaje :: Mover { x, y } => println! ( "Moviendo a ({x}, {y})" ),
Mensaje :: Escribir (texto) => println! ( "Escribiendo: {texto}" ),
Mensaje :: CambiarColor (r, g, b) => println! ( "Cambiando color a RGB({r}, {g}, {b})" ),
}
}
Enums con Métodos
#[derive( Debug )]
enum Forma {
Rectangulo { ancho : f64 , alto : f64 },
Circulo { radio : f64 },
Triangulo { base : f64 , altura : f64 },
}
impl Forma {
fn area ( & self ) -> f64 {
match self {
Forma :: Rectangulo { ancho, alto } => ancho * alto,
Forma :: Circulo { radio } => std :: f64 :: consts :: PI * radio * radio,
Forma :: Triangulo { base, altura } => 0.5 * base * altura,
}
}
fn perimetro ( & self ) -> f64 {
match self {
Forma :: Rectangulo { ancho, alto } => 2.0 * (ancho + alto),
Forma :: Circulo { radio } => 2.0 * std :: f64 :: consts :: PI * radio,
Forma :: Triangulo { base, altura } => {
// Asumimos triángulo rectángulo para simplificar
let hipotenusa = (base * base + altura * altura) . sqrt ();
base + altura + hipotenusa
}
}
}
fn descripcion ( & self ) -> String {
match self {
Forma :: Rectangulo { ancho, alto } => {
format! ( "Rectángulo {}x{}" , ancho, alto)
}
Forma :: Circulo { radio } => {
format! ( "Círculo con radio {}" , radio)
}
Forma :: Triangulo { base, altura } => {
format! ( "Triángulo {}x{}" , base, altura)
}
}
}
}
fn main () {
let formas = vec! [
Forma :: Rectangulo { ancho : 10.0 , alto : 5.0 },
Forma :: Circulo { radio : 3.0 },
Forma :: Triangulo { base : 4.0 , altura : 3.0 },
];
for forma in & formas {
println! ( "{}" , forma . descripcion ());
println! ( " Área: {:.2}" , forma . area ());
println! ( " Perímetro: {:.2}" , forma . perimetro ());
println! ();
}
}
El Enum Option
Option<T> es uno de los enums más importantes en Rust, usado para valores que pueden existir o no:
fn dividir (a : f64 , b : f64 ) -> Option < f64 > {
if b != 0.0 {
Some (a / b)
} else {
None
}
}
fn main () {
let resultados = vec! [
dividir ( 10.0 , 2.0 ),
dividir ( 5.0 , 0.0 ),
dividir ( 8.0 , 4.0 ),
];
for (i, resultado) in resultados . iter () . enumerate () {
match resultado {
Some (valor) => println! ( "Resultado {}: {:.2}" , i + 1 , valor),
None => println! ( "Resultado {}: División por cero" , i + 1 ),
}
}
// Métodos útiles de Option
let numero = Some ( 5 );
// unwrap: extrae el valor o entra en pánico
println! ( "Valor: {}" , numero . unwrap ());
// unwrap_or: valor por defecto si es None
let nada : Option < i32 > = None ;
println! ( "Valor o defecto: {}" , nada . unwrap_or ( 0 ));
// map: transforma el valor si existe
let doble = numero . map ( | x | x * 2 );
println! ( "Doble: {:?}" , doble);
// and_then: encadena operaciones que retornan Option
let cuadrado_si_par = numero . and_then ( | x | {
if x % 2 == 0 {
Some (x * x)
} else {
None
}
});
println! ( "Cuadrado si par: {:?}" , cuadrado_si_par);
}
El Enum Result<T, E>
Result<T, E> se usa para operaciones que pueden fallar:
#[derive( Debug )]
enum MiError {
DivisionPorCero ,
NumeroNegativo ,
Overflow ,
}
fn operacion_segura (a : i32 , b : i32 ) -> Result < i32 , MiError > {
if b == 0 {
return Err ( MiError :: DivisionPorCero );
}
if a < 0 || b < 0 {
return Err ( MiError :: NumeroNegativo );
}
let resultado = a . checked_mul (b);
match resultado {
Some (valor) => Ok (valor),
None => Err ( MiError :: Overflow ),
}
}
fn main () {
let operaciones = vec! [
( 4 , 5 ),
( 10 , 0 ),
( - 5 , 3 ),
( 1000000 , 1000000 ),
];
for (a, b) in operaciones {
match operacion_segura (a, b) {
Ok (resultado) => println! ( "{} * {} = {}" , a, b, resultado),
Err (error) => println! ( "{} * {} = Error: {:?}" , a, b, error),
}
}
// Métodos útiles de Result
let resultado = operacion_segura ( 3 , 4 );
// unwrap: extrae el valor o entra en pánico
println! ( "Resultado: {}" , resultado . unwrap ());
// expect: como unwrap pero con mensaje personalizado
let resultado2 = operacion_segura ( 2 , 6 );
println! ( "Resultado: {}" , resultado2 . expect ( "Error en la operación" ));
// unwrap_or: valor por defecto en caso de error
let resultado3 = operacion_segura ( 5 , 0 );
println! ( "Resultado o defecto: {}" , resultado3 . unwrap_or ( - 1 ));
}
Pattern Matching Avanzado
Guards en match
fn clasificar_numero (num : i32 ) -> String {
match num {
n if n < 0 => "Negativo" . to_string (),
0 => "Cero" . to_string (),
n if n > 0 && n <= 10 => "Pequeño positivo" . to_string (),
n if n > 10 && n <= 100 => "Medio" . to_string (),
n if n % 2 == 0 => "Grande y par" . to_string (),
_ => "Grande e impar" . to_string (),
}
}
fn main () {
let numeros = vec! [ - 5 , 0 , 3 , 15 , 50 , 123 , 100 ];
for num in numeros {
println! ( "{}: {}" , num, clasificar_numero (num));
}
}
Destructuring Complejo
#[derive( Debug )]
enum Evento {
Click { x : i32 , y : i32 , boton : Boton },
Teclado { tecla : char , modificadores : Vec < String > },
Scroll { delta : i32 },
}
#[derive( Debug )]
enum Boton {
Izquierdo ,
Derecho ,
Medio ,
}
fn manejar_evento (evento : Evento ) {
match evento {
// Destructuring específico
Evento :: Click { x : 0 , y : 0 , boton } => {
println! ( "Click en origen con botón {:?}" , boton);
}
// Destructuring con guards
Evento :: Click { x, y, boton : Boton :: Izquierdo } if x > 100 && y > 100 => {
println! ( "Click izquierdo en área especial ({}, {})" , x, y);
}
// Destructuring general
Evento :: Click { x, y, boton } => {
println! ( "Click {:?} en ({}, {})" , boton, x, y);
}
// Destructuring de Vec
Evento :: Teclado { tecla, modificadores } if modificadores . len () > 0 => {
println! ( "Tecla '{}' con modificadores: {:?}" , tecla, modificadores);
}
Evento :: Teclado { tecla, .. } => {
println! ( "Tecla simple: '{}'" , tecla);
}
Evento :: Scroll { delta } => {
let direccion = if delta > 0 { "arriba" } else { "abajo" };
println! ( "Scroll {} ({})" , direccion, delta . abs ());
}
}
}
fn main () {
let eventos = vec! [
Evento :: Click { x : 0 , y : 0 , boton : Boton :: Izquierdo },
Evento :: Click { x : 150 , y : 200 , boton : Boton :: Izquierdo },
Evento :: Click { x : 50 , y : 75 , boton : Boton :: Derecho },
Evento :: Teclado {
tecla : 'a' ,
modificadores : vec! [ "Ctrl" . to_string (), "Shift" . to_string ()]
},
Evento :: Teclado { tecla : 'x' , modificadores : vec! [] },
Evento :: Scroll { delta : - 5 },
];
for evento in eventos {
manejar_evento (evento);
}
}
if let y while let
Para casos donde solo te interesa un patrón específico:
fn main () {
let config_max = Some ( 3 u8 );
// En lugar de match completo
if let Some (max) = config_max {
println! ( "El máximo configurado es: {}" , max);
}
// Con else
let numero : Option < i32 > = None ;
if let Some (n) = numero {
println! ( "El número es: {}" , n);
} else {
println! ( "No hay número" );
}
// while let para procesar hasta que no haya más elementos
let mut pila = vec! [ 1 , 2 , 3 ];
while let Some (tope) = pila . pop () {
println! ( "Procesando: {}" , tope);
}
// Procesando Result con if let
let resultado : Result < i32 , & str > = Ok ( 42 );
if let Ok (valor) = resultado {
println! ( "Éxito: {}" , valor);
}
if let Err (error) = resultado {
println! ( "Error: {}" , error);
}
}
Ejercicios Prácticos
Ejercicio 1: Sistema de Notificaciones
#[derive( Debug )]
enum Notificacion {
Email {
destinatario : String ,
asunto : String ,
cuerpo : String
},
SMS {
numero : String ,
mensaje : String
},
Push {
titulo : String ,
contenido : String ,
badge : Option < u32 >
},
}
impl Notificacion {
fn enviar ( & self ) -> Result <(), String > {
match self {
Notificacion :: Email { destinatario, asunto, .. } => {
if destinatario . contains ( '@' ) {
println! ( "📧 Email enviado a {}: {}" , destinatario, asunto);
Ok (())
} else {
Err ( "Email inválido" . to_string ())
}
}
Notificacion :: SMS { numero, mensaje } => {
if numero . len () >= 10 {
println! ( "📱 SMS enviado a {}: {}" , numero, mensaje);
Ok (())
} else {
Err ( "Número de teléfono inválido" . to_string ())
}
}
Notificacion :: Push { titulo, contenido, badge } => {
let badge_text = match badge {
Some (n) => format! ( " [{}]" , n),
None => String :: new (),
};
println! ( "🔔 Push{}: {} - {}" , badge_text, titulo, contenido);
Ok (())
}
}
}
fn costo ( & self ) -> f64 {
match self {
Notificacion :: Email { .. } => 0.01 ,
Notificacion :: SMS { .. } => 0.05 ,
Notificacion :: Push { .. } => 0.02 ,
}
}
}
fn main () {
let notificaciones = vec! [
Notificacion :: Email {
destinatario : "usuario@ejemplo.com" . to_string (),
asunto : "Bienvenido" . to_string (),
cuerpo : "Gracias por registrarte" . to_string (),
},
Notificacion :: SMS {
numero : "123456789" . to_string (),
mensaje : "Código de verificación: 1234" . to_string (),
},
Notificacion :: Push {
titulo : "Nueva actualización" . to_string (),
contenido : "Hay funcionalidades nuevas disponibles" . to_string (),
badge : Some ( 3 ),
},
];
let mut costo_total = 0.0 ;
for notificacion in & notificaciones {
match notificacion . enviar () {
Ok (()) => {
costo_total += notificacion . costo ();
println! ( " Costo: ${:.2}" , notificacion . costo ());
}
Err (error) => {
println! ( " ❌ Error: {}" , error);
}
}
println! ();
}
println! ( "Costo total: ${:.2}" , costo_total);
}
Ejercicio 2: Calculadora con Manejo de Errores
#[derive( Debug )]
enum OperacionMatematica {
Sumar ( f64 , f64 ),
Restar ( f64 , f64 ),
Multiplicar ( f64 , f64 ),
Dividir ( f64 , f64 ),
Potencia ( f64 , f64 ),
RaizCuadrada ( f64 ),
}
#[derive( Debug )]
enum ErrorCalculo {
DivisionPorCero ,
RaizNegativa ,
Overflow ,
ResultadoInvalido ,
}
impl OperacionMatematica {
fn calcular ( & self ) -> Result < f64 , ErrorCalculo > {
match self {
OperacionMatematica :: Sumar (a, b) => Ok (a + b),
OperacionMatematica :: Restar (a, b) => Ok (a - b),
OperacionMatematica :: Multiplicar (a, b) => {
let resultado = a * b;
if resultado . is_infinite () {
Err ( ErrorCalculo :: Overflow )
} else {
Ok (resultado)
}
}
OperacionMatematica :: Dividir (a, b) => {
if * b == 0.0 {
Err ( ErrorCalculo :: DivisionPorCero )
} else {
let resultado = a / b;
if resultado . is_nan () || resultado . is_infinite () {
Err ( ErrorCalculo :: ResultadoInvalido )
} else {
Ok (resultado)
}
}
}
OperacionMatematica :: Potencia (base, exponente) => {
let resultado = base . powf ( * exponente);
if resultado . is_nan () || resultado . is_infinite () {
Err ( ErrorCalculo :: ResultadoInvalido )
} else {
Ok (resultado)
}
}
OperacionMatematica :: RaizCuadrada (n) => {
if * n < 0.0 {
Err ( ErrorCalculo :: RaizNegativa )
} else {
Ok (n . sqrt ())
}
}
}
}
fn descripcion ( & self ) -> String {
match self {
OperacionMatematica :: Sumar (a, b) => format! ( "{} + {}" , a, b),
OperacionMatematica :: Restar (a, b) => format! ( "{} - {}" , a, b),
OperacionMatematica :: Multiplicar (a, b) => format! ( "{} * {}" , a, b),
OperacionMatematica :: Dividir (a, b) => format! ( "{} / {}" , a, b),
OperacionMatematica :: Potencia (base, exp) => format! ( "{}^{}" , base, exp),
OperacionMatematica :: RaizCuadrada (n) => format! ( "√{}" , n),
}
}
}
fn main () {
let operaciones = vec! [
OperacionMatematica :: Sumar ( 10.0 , 5.0 ),
OperacionMatematica :: Dividir ( 15.0 , 3.0 ),
OperacionMatematica :: Dividir ( 10.0 , 0.0 ),
OperacionMatematica :: RaizCuadrada ( 16.0 ),
OperacionMatematica :: RaizCuadrada ( - 4.0 ),
OperacionMatematica :: Potencia ( 2.0 , 10.0 ),
OperacionMatematica :: Multiplicar ( f64 :: MAX , 2.0 ),
];
for operacion in operaciones {
print! ( "{} = " , operacion . descripcion ());
match operacion . calcular () {
Ok (resultado) => println! ( "{:.2}" , resultado),
Err (error) => {
let mensaje = match error {
ErrorCalculo :: DivisionPorCero => "Error: División por cero" ,
ErrorCalculo :: RaizNegativa => "Error: Raíz de número negativo" ,
ErrorCalculo :: Overflow => "Error: Desbordamiento numérico" ,
ErrorCalculo :: ResultadoInvalido => "Error: Resultado no válido" ,
};
println! ( "{}" , mensaje);
}
}
}
}
Ejercicio 3: Sistema de Estados de Juego
#[derive( Debug , Clone )]
enum EstadoJuego {
Menu ,
Jugando {
nivel : u32 ,
puntuacion : u32 ,
vidas : u32
},
Pausa {
nivel : u32 ,
puntuacion : u32 ,
vidas : u32
},
GameOver {
puntuacion_final : u32
},
Victoria {
puntuacion_final : u32 ,
tiempo : u32
},
}
#[derive( Debug )]
enum AccionJuego {
IniciarJuego ,
PausarJuego ,
ReanudarJuego ,
GanarPunto ( u32 ),
PerderVida ,
CompletarNivel ,
VolverAlMenu ,
Reiniciar ,
}
impl EstadoJuego {
fn procesar_accion ( &mut self , accion : AccionJuego ) -> Result <(), String > {
let nuevo_estado = match ( & self , accion) {
// Desde Menu
( EstadoJuego :: Menu , AccionJuego :: IniciarJuego ) => {
EstadoJuego :: Jugando { nivel : 1 , puntuacion : 0 , vidas : 3 }
}
// Desde Jugando
( EstadoJuego :: Jugando { nivel, puntuacion, vidas }, AccionJuego :: PausarJuego ) => {
EstadoJuego :: Pausa { nivel : * nivel, puntuacion : * puntuacion, vidas : * vidas }
}
( EstadoJuego :: Jugando { nivel, puntuacion, vidas }, AccionJuego :: GanarPunto (puntos)) => {
EstadoJuego :: Jugando {
nivel : * nivel,
puntuacion : puntuacion + puntos,
vidas : * vidas
}
}
( EstadoJuego :: Jugando { nivel, puntuacion, vidas }, AccionJuego :: PerderVida ) => {
if * vidas <= 1 {
EstadoJuego :: GameOver { puntuacion_final : * puntuacion }
} else {
EstadoJuego :: Jugando {
nivel : * nivel,
puntuacion : * puntuacion,
vidas : vidas - 1
}
}
}
( EstadoJuego :: Jugando { nivel, puntuacion, vidas }, AccionJuego :: CompletarNivel ) => {
if * nivel >= 10 {
EstadoJuego :: Victoria { puntuacion_final : * puntuacion, tiempo : 300 }
} else {
EstadoJuego :: Jugando {
nivel : nivel + 1 ,
puntuacion : puntuacion + 100 , // Bonus por nivel
vidas : * vidas
}
}
}
// Desde Pausa
( EstadoJuego :: Pausa { nivel, puntuacion, vidas }, AccionJuego :: ReanudarJuego ) => {
EstadoJuego :: Jugando { nivel : * nivel, puntuacion : * puntuacion, vidas : * vidas }
}
// Acciones globales
(_, AccionJuego :: VolverAlMenu ) => EstadoJuego :: Menu ,
(_, AccionJuego :: Reiniciar ) => EstadoJuego :: Menu ,
// Combinaciones inválidas
(estado_actual, accion) => {
return Err ( format! ( "Acción {:?} no válida en estado {:?}" , accion, estado_actual));
}
};
* self = nuevo_estado;
Ok (())
}
fn descripcion ( & self ) -> String {
match self {
EstadoJuego :: Menu => "En el menú principal" . to_string (),
EstadoJuego :: Jugando { nivel, puntuacion, vidas } => {
format! ( "Jugando - Nivel: {}, Puntuación: {}, Vidas: {}" , nivel, puntuacion, vidas)
}
EstadoJuego :: Pausa { nivel, puntuacion, vidas } => {
format! ( "En pausa - Nivel: {}, Puntuación: {}, Vidas: {}" , nivel, puntuacion, vidas)
}
EstadoJuego :: GameOver { puntuacion_final } => {
format! ( "Game Over - Puntuación final: {}" , puntuacion_final)
}
EstadoJuego :: Victoria { puntuacion_final, tiempo } => {
format! ( "¡Victoria! Puntuación: {}, Tiempo: {}s" , puntuacion_final, tiempo)
}
}
}
}
fn main () {
let mut juego = EstadoJuego :: Menu ;
let acciones = vec! [
AccionJuego :: IniciarJuego ,
AccionJuego :: GanarPunto ( 50 ),
AccionJuego :: GanarPunto ( 25 ),
AccionJuego :: PausarJuego ,
AccionJuego :: ReanudarJuego ,
AccionJuego :: PerderVida ,
AccionJuego :: CompletarNivel ,
AccionJuego :: GanarPunto ( 75 ),
AccionJuego :: CompletarNivel ,
AccionJuego :: VolverAlMenu ,
];
println! ( "Estado inicial: {}" , juego . descripcion ());
println! ();
for (i, accion) in acciones . iter () . enumerate () {
println! ( "Acción {}: {:?}" , i + 1 , accion);
match juego . procesar_accion (accion . clone ()) {
Ok (()) => println! ( " ✅ {}" , juego . descripcion ()),
Err (error) => println! ( " ❌ {}" , error),
}
println! ();
}
}
Puntos Clave para Recordar
Los enums pueden contener datos de diferentes tipos y estructuras
Option<T> reemplaza valores nulos de forma segura
Result<T, E> maneja errores de forma explícita
match debe ser exhaustivo - cubrir todos los casos posibles
Usa guards para condiciones adicionales en patterns
if let y while let simplifican casos específicos
Los enums pueden tener métodos como las structs
Destructuring permite extraer datos de variantes complejas
Combina enums con structs para modelado de datos poderoso
Anterior
Structs
Siguiente
Temas Avanzados Roadmap