Los enums de Rust no son simples constantes numéricas como en C o Java. Cada variante puede llevar datos de tipos distintos, lo que los convierte en algebraic data types (tipos de datos algebraicos). Son una de las características más expresivas del lenguaje y la base de Option y Result.
Enum básico sin datos
enum Direccion {
Norte,
Sur,
Este,
Oeste,
}
fn mover(d: Direccion) {
match d {
Direccion::Norte => println!("Subiendo"),
Direccion::Sur => println!("Bajando"),
Direccion::Este => println!("Derecha"),
Direccion::Oeste => println!("Izquierda"),
}
}
fn main() {
mover(Direccion::Norte);
}
Variantes con datos: IpAddr
Cada variante puede llevar datos distintos. No necesitas un struct separado:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let local = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
match local {
IpAddr::V4(a, b, c, d) => println!("{}.{}.{}.{}", a, b, c, d),
IpAddr::V6(s) => println!("{}", s),
}
}
Cada variante es una especie de constructor. IpAddr::V4 recibe cuatro u8; IpAddr::V6 recibe un String. Los tipos son completamente independientes.
Variantes con structs anónimos
enum Mensaje {
Salir,
Mover { x: i32, y: i32 },
Escribir(String),
CambiarColor(u8, u8, u8),
}
fn procesar(m: Mensaje) {
match m {
Mensaje::Salir => println!("Salir"),
Mensaje::Mover { x, y } => println!("Mover a ({}, {})", x, y),
Mensaje::Escribir(texto) => println!("Escribir: {}", texto),
Mensaje::CambiarColor(r, g, b) => println!("Color: #{:02X}{:02X}{:02X}", r, g, b),
}
}
Una sola variante puede combinar campos con nombre (como un struct) o posicionales (como una tupla).
Métodos en enums con impl
Al igual que los structs, los enums pueden tener métodos:
enum Moneda {
Penique,
Niquel,
Dime,
Cuarto,
}
impl Moneda {
fn valor_en_centimos(&self) -> u32 {
match self {
Moneda::Penique => 1,
Moneda::Niquel => 5,
Moneda::Dime => 10,
Moneda::Cuarto => 25,
}
}
}
fn main() {
let m = Moneda::Cuarto;
println!("{} centimos", m.valor_en_centimos());
}
Por qué se llaman algebraic data types
En teoría de tipos, un product type combina varios valores a la vez (como un struct: tiene campo A y campo B). Un sum type representa alternativas (tiene campo A o campo B). Los enums de Rust son sum types: un valor del enum es exactamente una de sus variantes, y solo lleva los datos de esa variante.
Esta dualidad (structs = product, enums = sum) te permite modelar cualquier dominio de datos con precisión:
// Árbol binario modelado con un enum
enum Arbol {
Hoja(i32),
Nodo { valor: i32, izq: Box<Arbol>, der: Box<Arbol> },
}
fn suma(a: &Arbol) -> i32 {
match a {
Arbol::Hoja(v) => *v,
Arbol::Nodo { valor, izq, der } => valor + suma(izq) + suma(der),
}
}
Option y Result son enums
Dos de los tipos más usados de la stdlib son enums:
// De la stdlib
enum Option<T> {
None,
Some(T),
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Option reemplaza al null; Result reemplaza a las excepciones. Ambos se tratan en profundidad en los siguientes artículos.
Resumen
- Los enums de Rust son algebraic data types: cada variante puede llevar datos de tipos distintos.
- Las variantes pueden ser simples, con tupla, con struct anónimo o sin datos.
- Los enums pueden tener métodos con
impl. Option<T>yResult<T, E>son enums de la stdlib.
El siguiente artículo profundiza en Option<T>: cómo usarla con match, if let, unwrap_or, map y el operador ? para manejar la ausencia de valores sin null.
