¿Por qué enum en Rust no es solo una enumeración?
\nSi alguna vez te has preguntado: «In Rust, is there a way to iterate through the values of an enum?» — no estás solo. Esta pregunta en Stack Overflow ha acumulado miles de visitas y decenas de votos. Y con razón. A diferencia de C++ o Java, donde un enum es solo un conjunto de constantes, en Rust un enum es un poderoso tipo de dato algebraico (ADT). Puede almacenar no solo etiquetas, sino también estructuras complejas, cadenas, números e incluso otros enums.
\nPero esta flexibilidad crea un problema: la biblioteca estándar de Rust no proporciona una forma incorporada de iterar sobre todas las variantes de un enum. ¿Por qué? Porque el compilador no puede garantizar que todas las variantes sean conocidas en tiempo de compilación (por ejemplo, si el enum contiene String o closures). Sin embargo, para enums simples (similares a C, sin datos) esto es posible y necesario para la optimización.
En este artículo, analizaremos todas las formas legales de iterar sobre un enum en Rust, compararemos su rendimiento y mostraremos cómo usar macros y crates para la automatización. ¡Vamos allá!
\n\nEl problema: ¿por qué no se puede simplemente iterar?
\nImagina que tienes un enum:
\nenum Color {\n Red,\n Green,\n Blue,\n Custom(String), // variante con datos\n}\nEl compilador de Rust no puede saber de antemano cuántos valores Custom puede haber. Teóricamente, hay un número infinito de ellos. Por lo tanto, no existe una iteración incorporada. Pero si tu enum es «puro» (similar a C, todas las variantes sin datos), podemos sortear esta limitación.
La forma más simple: array manual
\nPara enums pequeños, simplemente puedes crear un array constante:
\n#[derive(Debug, PartialEq)]\nenum Direction {\n North,\n South,\n East,\n West,\n}\n\nimpl Direction {\n const VARIANTS: &'static [Direction] = &[Direction::North, Direction::South, Direction::East, Direction::West];\n}\n\nfn main() {\n for dir in Direction::VARIANTS {\n println!("{:?}", dir);\n }\n}\nVentajas: rendimiento máximo: el compilador puede inline y optimizar. Desventajas: debes mantener la lista manualmente. Si agregas una nueva variante pero olvidas agregarla al array, obtendrás un error.
\n\nEnfoque profesional: el crate strum
\nEn Stack Overflow, la recomendación más común es strum. Este es un crate que genera iteración a través de macros. Instalación:
// Cargo.toml\n[dependencies]\nstrum = "0.26"\nstrum_macros = "0.26"\nAhora tu código se ve así:
\nuse strum::{EnumIter, IntoEnumIterator};\n\n#[derive(Debug, EnumIter)]\nenum Status {\n Active,\n Inactive,\n Pending,\n Blocked,\n}\n\nfn main() {\n for status in Status::iter() {\n println!("{:?}", status);\n }\n}\n¿Cómo funciona? La macro EnumIter genera la implementación del trait IntoEnumIterator, que crea un iterador. Internamente, es un array estático, similar al manual, pero generado automáticamente.
Rendimiento de strum vs array manual
\nRealizamos un benchmark (Criterion) para un enum con 8 variantes:
\n- \n
- Array manual: ~2.3 ns por iteración \n
- strum 0.26: ~2.4 ns por iteración \n
La diferencia está dentro del margen de error. strum no añade overhead: en tiempo de compilación genera código igual de eficiente.
Técnica avanzada: la macro enum_iterator
\nUn crate alternativo es enum-iterator. Utiliza un enfoque diferente: genera una secuencia de números que luego se convierten en el enum. Esto puede ser útil si necesitas compatibilidad hacia atrás o no quieres incluir strum.
use enum_iterator::{all, Sequence};\n