Rust

Rust

Tutorial Rust: Enumeraciones enums

Descubre cómo usar enumeraciones enums en Rust para modelar datos y comportamientos complejos con ejemplos prácticos y métodos.

Aprende Rust y certifícate

Variantes simples

Las enumeraciones (o enums) son un tipo de dato en Rust que permite definir un tipo que puede ser uno de varios valores posibles, llamados variantes. Imagina un tipo que representa los días de la semana o los estados de un proceso - estos son casos perfectos para usar enums.

En su forma más básica, los enums en Rust son similares a los enums que encontrarías en lenguajes como C o Java. Veamos cómo se definen:

enum DiaSemana {
    Lunes,
    Martes,
    Miercoles,
    Jueves,
    Viernes,
    Sabado,
    Domingo,
}

En este ejemplo, hemos creado un tipo DiaSemana que puede tener exactamente uno de los siete valores definidos. Cada variante (Lunes, Martes, etc.) se convierte en un posible valor para este tipo.

Para crear un valor de este enum, usamos la sintaxis con el operador de ámbito :::

fn main() {
    let dia = DiaSemana::Miercoles;
    
    // Podemos usar el enum en una expresión if con comparaciones
    if dia == DiaSemana::Viernes {
        println!("¡Por fin es viernes!");
    } else {
        println!("Todavía no es viernes...");
    }
}

Los enums son especialmente útiles cuando queremos representar un conjunto fijo de posibilidades. Por ejemplo, podríamos representar los estados de una tarea:

enum EstadoTarea {
    NuevaCreada,
    EnProgreso,
    EnRevision,
    Completada,
    Cancelada,
}

fn main() {
    let mi_tarea = EstadoTarea::EnProgreso;
    
    // Podemos usar un enum en una estructura de control básica
    match mi_tarea {
        EstadoTarea::Completada => println!("¡La tarea está terminada!"),
        EstadoTarea::Cancelada => println!("La tarea fue cancelada"),
        _ => println!("La tarea sigue en proceso"),
    }
}

En este ejemplo, usamos la expresión match para realizar diferentes acciones según el valor del enum. El patrón _ actúa como un comodín que captura cualquier valor no especificado anteriormente.

Enums con valores explícitos

En algunos casos, podemos querer asignar valores específicos a las variantes de un enum, similar a cómo funcionan en C:

enum Codigo {
    Ok = 200,
    NotFound = 404,
    ServerError = 500,
}

fn main() {
    // Podemos obtener el valor numérico subyacente
    let codigo_respuesta = Codigo::NotFound as i32;
    println!("Código de estado: {}", codigo_respuesta); // Imprime: Código de estado: 404
}

Esto es útil cuando necesitamos interoperar con sistemas que esperan valores numéricos específicos, como códigos de estado HTTP o códigos de error.

Enums para modelar dominios

Los enums simples son excelentes para modelar dominios donde tenemos un conjunto fijo de posibilidades sin datos adicionales. Por ejemplo, podríamos representar los tipos de usuario en un sistema:

enum TipoUsuario {
    Administrador,
    Editor,
    Autor,
    Suscriptor,
    Invitado,
}

fn verificar_permisos(usuario: TipoUsuario, accion: &str) -> bool {
    match (usuario, accion) {
        (TipoUsuario::Administrador, _) => true, // Los administradores pueden hacer cualquier cosa
        (TipoUsuario::Editor, "editar") => true,
        (TipoUsuario::Editor, "publicar") => true,
        (TipoUsuario::Autor, "editar") => true,
        _ => false, // Cualquier otra combinación no está permitida
    }
}

fn main() {
    let usuario = TipoUsuario::Autor;
    
    if verificar_permisos(usuario, "editar") {
        println!("Permiso concedido para editar");
    } else {
        println!("Permiso denegado");
    }
    
    if verificar_permisos(usuario, "publicar") {
        println!("Permiso concedido para publicar");
    } else {
        println!("Permiso denegado");
    }
}

En este ejemplo, usamos un enum para representar los diferentes tipos de usuarios y luego implementamos una función que verifica los permisos basándose en el tipo de usuario y la acción solicitada.

Iterando sobre enums

A diferencia de algunos otros lenguajes, Rust no proporciona una forma automática de iterar sobre todas las variantes de un enum. Sin embargo, podemos implementar esta funcionalidad nosotros mismos si es necesario:

enum Direccion {
    Norte,
    Sur,
    Este,
    Oeste,
}

fn main() {
    // Creamos un array con todas las variantes
    let direcciones = [
        Direccion::Norte,
        Direccion::Sur,
        Direccion::Este,
        Direccion::Oeste,
    ];
    
    // Ahora podemos iterar sobre el array
    for dir in &direcciones {
        match dir {
            Direccion::Norte => println!("Viajando hacia el norte"),
            Direccion::Sur => println!("Viajando hacia el sur"),
            Direccion::Este => println!("Viajando hacia el este"),
            Direccion::Oeste => println!("Viajando hacia el oeste"),
        }
    }
}

Los enums simples son una herramienta fundamental en Rust para modelar dominios con un conjunto fijo de posibilidades. En la siguiente sección, veremos cómo Rust lleva los enums al siguiente nivel permitiendo asociar datos a cada variante, lo que los hace mucho más poderosos que los enums tradicionales de otros lenguajes.

Variantes con datos

Mientras que los enums simples son útiles para representar un conjunto fijo de valores, Rust lleva este concepto mucho más allá permitiendo que cada variante del enum pueda contener datos asociados. Esta característica distintiva de Rust hace que los enums sean extremadamente versátiles y expresivos.

En lugar de limitarse a ser simples etiquetas, las variantes de un enum pueden contener cualquier tipo de datos, incluyendo tipos primitivos, estructuras, tuplas e incluso otros enums. Cada variante puede tener su propia estructura de datos independiente.

Enums con datos homogéneos

Comencemos con un ejemplo sencillo donde todas las variantes contienen el mismo tipo de datos:

enum Calificacion {
    Sobresaliente(f32),
    Notable(f32),
    Aprobado(f32),
    Suspenso(f32),
}

fn main() {
    let nota_programacion = Calificacion::Notable(8.5);
    
    // Podemos extraer el valor usando match
    let mensaje = match nota_programacion {
        Calificacion::Sobresaliente(valor) => format!("¡Excelente! Has sacado un {}", valor),
        Calificacion::Notable(valor) => format!("Muy bien, has obtenido un {}", valor),
        Calificacion::Aprobado(valor) => format!("Has aprobado con un {}", valor),
        Calificacion::Suspenso(valor) => format!("Has suspendido con un {}", valor),
    };
    
    println!("{}", mensaje);
}

En este ejemplo, cada variante del enum Calificacion contiene un valor de tipo f32 que representa la nota numérica. Cuando hacemos el match, podemos extraer y utilizar ese valor.

Enums con datos heterogéneos

La verdadera potencia de los enums en Rust se manifiesta cuando cada variante puede contener diferentes tipos de datos:

enum Mensaje {
    Texto(String),
    CodigoDeError(i32),
    Coordenadas(i32, i32),
    Configuracion { id: i32, valor: bool },
}

fn main() {
    let mensaje1 = Mensaje::Texto(String::from("Hola, ¿cómo estás?"));
    let mensaje2 = Mensaje::CodigoDeError(404);
    let mensaje3 = Mensaje::Coordenadas(10, 20);
    let mensaje4 = Mensaje::Configuracion { id: 1, valor: true };
    
    procesar_mensaje(mensaje1);
    procesar_mensaje(mensaje2);
}

fn procesar_mensaje(msg: Mensaje) {
    match msg {
        Mensaje::Texto(contenido) => println!("Mensaje de texto: {}", contenido),
        Mensaje::CodigoDeError(codigo) => println!("Error: {}", codigo),
        Mensaje::Coordenadas(x, y) => println!("Posición: ({}, {})", x, y),
        Mensaje::Configuracion { id, valor } => {
            println!("Configuración {}: {}", id, if valor { "activada" } else { "desactivada" })
        }
    }
}

Observa cómo cada variante puede tener una estructura completamente diferente:

  • Texto contiene una String
  • CodigoDeError contiene un entero
  • Coordenadas contiene una tupla de dos enteros
  • Configuracion contiene una estructura similar a un struct

Esta flexibilidad nos permite modelar conceptos complejos de manera elegante y segura.

Modelando estados con datos

Los enums con datos son perfectos para modelar estados que contienen información específica para cada estado:

enum EstadoJuego {
    Inicio,
    Jugando { puntuacion: i32, nivel: i32, vidas: i32 },
    Pausa { tiempo_transcurrido: f32 },
    GameOver { puntuacion_final: i32 },
}

fn main() {
    let mut estado = EstadoJuego::Inicio;
    
    // Simulamos cambios de estado
    estado = EstadoJuego::Jugando { puntuacion: 0, nivel: 1, vidas: 3 };
    
    // Después de jugar un rato
    if let EstadoJuego::Jugando { puntuacion, nivel, vidas } = estado {
        println!("Jugando nivel {} con {} puntos y {} vidas", nivel, puntuacion, vidas);
        
        // Perdemos el juego
        if vidas <= 0 {
            estado = EstadoJuego::GameOver { puntuacion_final: puntuacion };
        }
    }
}

En este ejemplo, el estado Jugando contiene toda la información relevante para ese estado específico, mientras que otros estados contienen información diferente o ninguna.

Enums recursivos

Una característica particularmente potente es que los enums pueden ser recursivos, es decir, pueden contener instancias de sí mismos. Esto es útil para representar estructuras de datos jerárquicas:

enum ArbolBinario {
    Hoja(i32),
    Nodo {
        valor: i32,
        izquierda: Box<ArbolBinario>,
        derecha: Box<ArbolBinario>,
    },
}

fn main() {
    // Creamos un árbol simple: 
    //      5
    //     / \
    //    3   7
    let arbol = ArbolBinario::Nodo {
        valor: 5,
        izquierda: Box::new(ArbolBinario::Hoja(3)),
        derecha: Box::new(ArbolBinario::Hoja(7)),
    };
    
    // Calculamos la suma de todos los valores
    let suma = suma_arbol(&arbol);
    println!("La suma de todos los valores es: {}", suma);
}

fn suma_arbol(arbol: &ArbolBinario) -> i32 {
    match arbol {
        ArbolBinario::Hoja(valor) => *valor,
        ArbolBinario::Nodo { valor, izquierda, derecha } => {
            *valor + suma_arbol(izquierda) + suma_arbol(derecha)
        }
    }
}

Aquí usamos Box<T> para crear un tipo de puntero inteligente que nos permite tener estructuras recursivas. Sin entrar en detalles de cómo funciona la gestión de memoria, esto nos permite crear estructuras de datos complejas como árboles.

Enums para resultados de operaciones

Un patrón común en Rust es usar enums para representar el resultado de operaciones que pueden fallar:

enum ResultadoDivision {
    Exito(f64),
    ErrorDivisionPorCero,
}

fn dividir(a: f64, b: f64) -> ResultadoDivision {
    if b == 0.0 {
        ResultadoDivision::ErrorDivisionPorCero
    } else {
        ResultadoDivision::Exito(a / b)
    }
}

fn main() {
    let resultado1 = dividir(10.0, 2.0);
    let resultado2 = dividir(5.0, 0.0);
    
    match resultado1 {
        ResultadoDivision::Exito(valor) => println!("El resultado es: {}", valor),
        ResultadoDivision::ErrorDivisionPorCero => println!("¡Error! División por cero"),
    }
    
    match resultado2 {
        ResultadoDivision::Exito(valor) => println!("El resultado es: {}", valor),
        ResultadoDivision::ErrorDivisionPorCero => println!("¡Error! División por cero"),
    }
}

Este patrón es tan útil que Rust tiene enums predefinidos extremadamente útiles como Option y Result que veremos en la próxima lección.

Usando if let para simplificar el manejo de enums

Cuando solo nos interesa una variante específica de un enum, podemos usar la sintaxis if let para simplificar nuestro código:

enum Configuracion {
    Simple,
    Avanzada { max_conexiones: i32, timeout: i32 },
    Personalizada(String),
}

fn main() {
    let config = Configuracion::Avanzada { max_conexiones: 100, timeout: 30 };
    
    // En lugar de un match completo:
    /*
    match config {
        Configuracion::Avanzada { max_conexiones, timeout } => {
            println!("Configuración avanzada: {} conexiones, {} segundos", max_conexiones, timeout);
        },
        _ => (), // No hacemos nada para otros casos
    }
    */
    
    // Podemos usar if let:
    if let Configuracion::Avanzada { max_conexiones, timeout } = config {
        println!("Configuración avanzada: {} conexiones, {} segundos", max_conexiones, timeout);
    }
}

La sintaxis if let es más concisa cuando solo nos interesa una variante específica y queremos ignorar las demás.

Los enums con datos son una de las características más distintivas y potentes de Rust, permitiéndonos modelar dominios complejos de manera segura y expresiva. En la siguiente sección, veremos cómo podemos añadir comportamiento a nuestros enums implementando métodos, igual que hacemos con los structs.

Métodos en enums

Al igual que con las estructuras (struct), Rust nos permite implementar métodos para nuestros enums. Esta capacidad convierte a los enums en tipos de datos no solo con diferentes variantes, sino también con comportamiento asociado, lo que aumenta significativamente su utilidad y expresividad.

La sintaxis para implementar métodos en enums es idéntica a la que ya conocemos para structs, utilizando el bloque impl:

enum Figura {
    Circulo(f64),
    Rectangulo(f64, f64),
    Triangulo(f64, f64, f64),
}

impl Figura {
    fn area(&self) -> f64 {
        match self {
            Figura::Circulo(radio) => std::f64::consts::PI * radio * radio,
            Figura::Rectangulo(ancho, alto) => ancho * alto,
            Figura::Triangulo(a, b, c) => {
                // Fórmula de Herón para el área de un triángulo
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()
            }
        }
    }
    
    fn describir(&self) -> String {
        match self {
            Figura::Circulo(radio) => format!("Círculo con radio {}", radio),
            Figura::Rectangulo(ancho, alto) => format!("Rectángulo de {}x{}", ancho, alto),
            Figura::Triangulo(a, b, c) => format!("Triángulo con lados {}, {} y {}", a, b, c),
        }
    }
}

fn main() {
    let circulo = Figura::Circulo(5.0);
    let rectangulo = Figura::Rectangulo(4.0, 6.0);
    
    println!("{}: área = {:.2}", circulo.describir(), circulo.area());
    println!("{}: área = {:.2}", rectangulo.describir(), rectangulo.area());
}

En este ejemplo, hemos definido dos métodos para nuestro enum Figura:

  • area() - calcula el área según el tipo de figura
  • describir() - genera una descripción textual de la figura

Métodos específicos para variantes

Aunque los métodos se implementan para todo el enum, podemos crear comportamientos específicos para cada variante utilizando patrones de coincidencia dentro de los métodos:

enum Mensaje {
    Texto(String),
    Audio { contenido: Vec<u8>, duracion_segundos: u32 },
    Imagen { url: String, ancho: u32, alto: u32 },
}

impl Mensaje {
    fn enviar(&self) -> bool {
        println!("Enviando mensaje...");
        
        // Comportamiento específico según la variante
        match self {
            Mensaje::Texto(contenido) => {
                println!("Enviando texto: {}", contenido);
                // Lógica para enviar texto
                true
            },
            Mensaje::Audio { contenido, duracion_segundos } => {
                println!("Enviando audio de {} segundos ({} bytes)", 
                         duracion_segundos, contenido.len());
                // Lógica para enviar audio
                true
            },
            Mensaje::Imagen { url, ancho, alto } => {
                println!("Enviando imagen {}x{} desde: {}", ancho, alto, url);
                // Lógica para enviar imagen
                if url.starts_with("http") {
                    true
                } else {
                    println!("Error: URL inválida");
                    false
                }
            }
        }
    }
    
    fn tamanio_bytes(&self) -> usize {
        match self {
            Mensaje::Texto(contenido) => contenido.len(),
            Mensaje::Audio { contenido, .. } => contenido.len(),
            Mensaje::Imagen { .. } => 0, // Asumimos que la imagen es un enlace externo
        }
    }
}

fn main() {
    let mensaje1 = Mensaje::Texto(String::from("Hola, ¿cómo estás?"));
    let mensaje2 = Mensaje::Imagen { 
        url: String::from("https://ejemplo.com/imagen.jpg"),
        ancho: 800,
        alto: 600
    };
    
    mensaje1.enviar();
    println!("Tamaño del mensaje: {} bytes", mensaje1.tamanio_bytes());
    
    mensaje2.enviar();
}

Métodos constructores

Una práctica común es implementar métodos constructores (funciones asociadas sin self) que facilitan la creación de instancias específicas del enum:

enum Resultado<T> {
    Ok(T),
    Error(String),
}

impl<T> Resultado<T> {
    // Método constructor para crear un resultado exitoso
    fn ok(valor: T) -> Resultado<T> {
        Resultado::Ok(valor)
    }
    
    // Método constructor para crear un resultado de error
    fn error(mensaje: &str) -> Resultado<T> {
        Resultado::Error(mensaje.to_string())
    }
    
    // Método para verificar si es un resultado exitoso
    fn es_ok(&self) -> bool {
        match self {
            Resultado::Ok(_) => true,
            Resultado::Error(_) => false,
        }
    }
    
    // Método para obtener el valor o un valor por defecto
    fn valor_o_defecto(&self, defecto: T) -> T 
    where 
        T: Clone,
    {
        match self {
            Resultado::Ok(valor) => valor.clone(),
            Resultado::Error(_) => defecto,
        }
    }
}

fn dividir(a: f64, b: f64) -> Resultado<f64> {
    if b == 0.0 {
        Resultado::error("División por cero")
    } else {
        Resultado::ok(a / b)
    }
}

fn main() {
    let resultado1 = dividir(10.0, 2.0);
    let resultado2 = dividir(5.0, 0.0);
    
    if resultado1.es_ok() {
        println!("La operación fue exitosa");
    }
    
    // Obtener el valor o un valor por defecto
    let valor1 = resultado1.valor_o_defecto(0.0);
    let valor2 = resultado2.valor_o_defecto(0.0);
    
    println!("Resultado 1: {}", valor1);
    println!("Resultado 2: {}", valor2);
}

Este patrón es similar al que utilizan los tipos Option y Result predefinidos en Rust.

Implementando comportamiento para máquinas de estado

Los enums son ideales para modelar máquinas de estado, y los métodos nos permiten definir las transiciones entre estados:

enum EstadoPedido {
    Recibido { timestamp: u64 },
    EnProceso { inicio_proceso: u64, operador_id: u32 },
    Enviado { codigo_seguimiento: String },
    Entregado { fecha_entrega: u64 },
    Cancelado { motivo: String },
}

impl EstadoPedido {
    fn nuevo(timestamp: u64) -> Self {
        EstadoPedido::Recibido { timestamp }
    }
    
    fn procesar(&self, operador_id: u32) -> EstadoPedido {
        match self {
            EstadoPedido::Recibido { timestamp } => {
                EstadoPedido::EnProceso { 
                    inicio_proceso: *timestamp, 
                    operador_id 
                }
            },
            _ => {
                println!("No se puede procesar un pedido que no está en estado 'Recibido'");
                self.clonar()
            }
        }
    }
    
    fn enviar(&self, codigo: &str) -> EstadoPedido {
        match self {
            EstadoPedido::EnProceso { .. } => {
                EstadoPedido::Enviado { 
                    codigo_seguimiento: codigo.to_string() 
                }
            },
            _ => {
                println!("Solo se pueden enviar pedidos en proceso");
                self.clonar()
            }
        }
    }
    
    fn entregar(&self, fecha: u64) -> EstadoPedido {
        match self {
            EstadoPedido::Enviado { .. } => {
                EstadoPedido::Entregado { fecha_entrega: fecha }
            },
            _ => {
                println!("Solo se pueden entregar pedidos enviados");
                self.clonar()
            }
        }
    }
    
    fn cancelar(&self, motivo: &str) -> EstadoPedido {
        match self {
            EstadoPedido::Entregado { .. } => {
                println!("No se puede cancelar un pedido ya entregado");
                self.clonar()
            },
            _ => EstadoPedido::Cancelado { motivo: motivo.to_string() }
        }
    }
    
    // Método auxiliar para clonar el estado actual
    fn clonar(&self) -> EstadoPedido {
        match self {
            EstadoPedido::Recibido { timestamp } => 
                EstadoPedido::Recibido { timestamp: *timestamp },
            EstadoPedido::EnProceso { inicio_proceso, operador_id } => 
                EstadoPedido::EnProceso { inicio_proceso: *inicio_proceso, operador_id: *operador_id },
            EstadoPedido::Enviado { codigo_seguimiento } => 
                EstadoPedido::Enviado { codigo_seguimiento: codigo_seguimiento.clone() },
            EstadoPedido::Entregado { fecha_entrega } => 
                EstadoPedido::Entregado { fecha_entrega: *fecha_entrega },
            EstadoPedido::Cancelado { motivo } => 
                EstadoPedido::Cancelado { motivo: motivo.clone() },
        }
    }
}

fn main() {
    // Simulamos el ciclo de vida de un pedido
    let mut pedido = EstadoPedido::nuevo(1634567890);
    
    // Transiciones válidas
    pedido = pedido.procesar(42);
    pedido = pedido.enviar("TRACK123456");
    pedido = pedido.entregar(1634657890);
    
    // Transición inválida (no debería cambiar el estado)
    pedido = pedido.cancelar("Ya no lo quiero");
}

En este ejemplo, cada método representa una transición válida entre estados y verifica que la transición sea permitida según el estado actual.

Métodos genéricos en enums

Los enums también pueden ser genéricos y tener métodos que trabajen con esos tipos genéricos:

enum Contenedor<T> {
    Lleno(T),
    Vacio,
}

impl<T> Contenedor<T> {
    fn nuevo() -> Self {
        Contenedor::Vacio
    }
    
    fn insertar(&self, valor: T) -> Self {
        Contenedor::Lleno(valor)
    }
    
    fn extraer(&self) -> Option<&T> {
        match self {
            Contenedor::Lleno(valor) => Some(valor),
            Contenedor::Vacio => None,
        }
    }
    
    fn esta_vacio(&self) -> bool {
        match self {
            Contenedor::Vacio => true,
            Contenedor::Lleno(_) => false,
        }
    }
}

fn main() {
    let contenedor: Contenedor<i32> = Contenedor::nuevo();
    println!("¿Está vacío? {}", contenedor.esta_vacio());
    
    let contenedor = contenedor.insertar(42);
    println!("¿Está vacío? {}", contenedor.esta_vacio());
    
    if let Some(valor) = contenedor.extraer() {
        println!("Valor extraído: {}", valor);
    }
}

Este patrón es similar a cómo funciona el tipo Option<T> en la biblioteca estándar de Rust.

Combinando métodos con pattern matching

Los métodos en enums se combinan perfectamente con el pattern matching para crear APIs expresivas:

enum Operacion {
    Suma(i32, i32),
    Resta(i32, i32),
    Multiplicacion(i32, i32),
    Division(i32, i32),
}

impl Operacion {
    fn ejecutar(&self) -> Option<i32> {
        match self {
            Operacion::Suma(a, b) => Some(a + b),
            Operacion::Resta(a, b) => Some(a - b),
            Operacion::Multiplicacion(a, b) => Some(a * b),
            Operacion::Division(a, b) => {
                if *b != 0 {
                    Some(a / b)
                } else {
                    None
                }
            }
        }
    }
    
    fn describir(&self) -> String {
        match self {
            Operacion::Suma(a, b) => format!("{} + {}", a, b),
            Operacion::Resta(a, b) => format!("{} - {}", a, b),
            Operacion::Multiplicacion(a, b) => format!("{} * {}", a, b),
            Operacion::Division(a, b) => format!("{} / {}", a, b),
        }
    }
}

fn main() {
    let operaciones = vec![
        Operacion::Suma(5, 3),
        Operacion::Resta(10, 4),
        Operacion::Multiplicacion(3, 6),
        Operacion::Division(8, 2),
        Operacion::Division(5, 0),
    ];
    
    for op in operaciones {
        match op.ejecutar() {
            Some(resultado) => println!("{} = {}", op.describir(), resultado),
            None => println!("{} = Error (división por cero)", op.describir()),
        }
    }
}

La combinación de enums con métodos y pattern matching crea un código que es a la vez seguro y expresivo, permitiéndonos modelar dominios complejos de manera elegante.

Los métodos en enums son una característica fundamental que complementa el sistema de tipos de Rust, permitiéndonos no solo definir qué datos pueden existir (a través de las variantes del enum), sino también qué comportamiento tienen esos datos. Esta combinación hace que los enums en Rust sean mucho más potentes que en la mayoría de los otros lenguajes de programación.

Aprende Rust online

Otras lecciones de Rust

Accede a todas las lecciones de Rust y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Rust y certifícate

Ejercicios de programación de Rust

Evalúa tus conocimientos de esta lección Enumeraciones enums con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la definición y uso básico de enums con variantes simples en Rust.
  • Aprender a asociar datos a variantes de enums para modelar dominios complejos.
  • Implementar métodos en enums para añadir comportamiento específico a las variantes.
  • Utilizar patrones de coincidencia (match) y sintaxis if let para manejar enums de forma segura y expresiva.
  • Aplicar enums para modelar máquinas de estado, estructuras recursivas y resultados de operaciones con manejo de errores.