Enums
Desde la versión del lenguaje 2.0
Los tipos enum son similares a los tipos struct pero soportan la definición de múltiples variantes del diseño de datos. Cada variante tiene su conjunto distinto de campos. Las variantes de enum son compatibles con expresiones, herramientas para pruebas, emparejamiento y deconstrucción.
Declaración de Tipos Enum
Sección titulada «Declaración de Tipos Enum»Una declaración de tipo enum lista el número de diferentes variantes, como se ve en el ejemplo a continuación:
enum Shape { Circle{radius: u64}, Rectangle{width: u64, height: u64}}
Puede haber cero o más campos para una variante de enum. Si no se proporcionan argumentos, las llaves también pueden omitirse, declarando valores simples:
enum Color { Red, Blue, Green}
Al igual que los tipos struct, los tipos enum pueden tener habilidades. Por ejemplo, el tipo enum Color
se declararía apropiadamente como copiable, desechable y almacenable, como los tipos numéricos primitivos:
enum Color has copy, drop, store, key { Red, Blue, Green }
Los tipos enum también pueden tener la habilidad key
y aparecer como raíces de datos en el almacenamiento global. Un uso común de los enums en este contexto es el versionado de datos:
enum VersionedData has key { V1{name: String} V2{name: String, age: u64}}
Similar a los structs, los tipos enum pueden ser genéricos y tomar argumentos posicionales. Por ejemplo, el tipo a continuación representa un tipo de resultado genérico, donde los constructores de variantes usan argumentos posicionales en lugar de nombrados (ver también structs posicionales).
enum Result<T> has copy, drop, store { Err(u64), Ok(T)}
Construcción de Valores Enum
Sección titulada «Construcción de Valores Enum»Un valor enum se construye de manera similar a un valor struct:
let s: String;let data = VersionedData::V1{name: s};
Si la variante enum no tiene campos, las llaves también pueden omitirse:
let color = Color::Blue;
Resolución de Nombres para Variantes Enum
Sección titulada «Resolución de Nombres para Variantes Enum»Los nombres de variantes para un enum necesitan ser calificados por el nombre del tipo enum, como en VersionedData::V1
.
Nota: El aliasing mediante la cláusula
use
actualmente no está soportado para variantes enum, pero se agregará en versiones posteriores del lenguaje
En ciertos casos (como las expresiones match, a continuación), el compilador de Move puede inferir el tipo enum del contexto, y la calificación por el nombre del tipo puede omitirse:
fun f(data: VersionedData) { match (data) { V1{..} => .., ..} // nombre de variante simple OK}
Operaciones en Enums
Sección titulada «Operaciones en Enums»Emparejamiento de Patrones
Sección titulada «Emparejamiento de Patrones»La principal manera de interactuar con valores enum es a través del emparejamiento de patrones usando expresiones match
. Una expresión match examina un valor enum y ejecuta diferentes códigos basados en cuál variante encuentra.
fun get_area(shape: Shape): u64 { match (shape) { Shape::Circle{radius} => 3 * radius * radius, // aproximación de π * r² Shape::Rectangle{width, height} => width * height, }}
Las expresiones match deben ser exhaustivas - deben manejar todas las posibles variantes:
fun describe_color(color: Color): String { match (color) { Color::Red => string::utf8(b"Rojo"), Color::Blue => string::utf8(b"Azul"), Color::Green => string::utf8(b"Verde"), }}
Emparejamiento con Comodines
Sección titulada «Emparejamiento con Comodines»Puedes usar patrones comodín para manejar múltiples casos:
fun is_primary_color(color: Color): bool { match (color) { Color::Red | Color::Blue => true, _ => false, // maneja todos los otros casos }}
Deconstrucción Parcial
Sección titulada «Deconstrucción Parcial»Puedes deconstruir parcialmente campos de enum usando la sintaxis ..
:
fun has_name(data: VersionedData, target: String): bool { match (data) { VersionedData::V1{name, ..} => name == target, VersionedData::V2{name, ..} => name == target, }}
Guardas en Emparejamiento
Sección titulada «Guardas en Emparejamiento»Las expresiones match soportan guardas para condiciones adicionales:
fun categorize_shape(shape: Shape): String { match (shape) { Shape::Circle{radius} if (radius > 10) => string::utf8(b"Círculo grande"), Shape::Circle{..} => string::utf8(b"Círculo pequeño"), Shape::Rectangle{width, height} if (width == height) => string::utf8(b"Cuadrado"), Shape::Rectangle{..} => string::utf8(b"Rectángulo"), }}
Casos de Uso Comunes
Sección titulada «Casos de Uso Comunes»Tipos de Resultado
Sección titulada «Tipos de Resultado»Los enums son perfectos para representar operaciones que pueden fallar:
enum Result<T, E> has copy, drop, store { Ok(T), Err(E)}
fun divide(x: u64, y: u64): Result<u64, String> { if (y == 0) { Result::Err(string::utf8(b"División por cero")) } else { Result::Ok(x / y) }}
fun handle_division(result: Result<u64, String>): u64 { match (result) { Result::Ok(value) => value, Result::Err(msg) => { // Manejar error 0 } }}
Tipos de Opción
Sección titulada «Tipos de Opción»Para representar valores que pueden estar presentes o ausentes:
enum Option<T> has copy, drop, store { None, Some(T)}
fun get_user_name(user_id: u64): Option<String> { if (user_exists(user_id)) { Option::Some(get_name(user_id)) } else { Option::None }}
fun display_name(name_opt: Option<String>): String { match (name_opt) { Option::Some(name) => name, Option::None => string::utf8(b"Usuario desconocido"), }}
Máquinas de Estado
Sección titulada «Máquinas de Estado»Los enums son excelentes para representar estados de máquina de estado:
enum OrderStatus has copy, drop, store { Pending{placed_at: u64}, Processing{started_at: u64}, Shipped{tracking_number: String}, Delivered{delivered_at: u64}, Cancelled{reason: String}}
fun process_order(status: OrderStatus): OrderStatus { match (status) { OrderStatus::Pending{..} => OrderStatus::Processing{started_at: timestamp::now_seconds()}, OrderStatus::Processing{..} => OrderStatus::Shipped{tracking_number: generate_tracking()}, _ => status, // No se puede procesar desde otros estados }}
Versionado de Datos
Sección titulada «Versionado de Datos»Para evolución de esquemas y migración de datos:
enum UserData has key { V1{name: String}, V2{name: String, email: String}, V3{name: String, email: String, preferences: UserPreferences}}
fun migrate_user_data(data: UserData): UserData { match (data) { UserData::V1{name} => UserData::V2{ name, email: string::utf8(b"") }, UserData::V2{name, email} => UserData::V3{ name, email, preferences: default_preferences() }, UserData::V3{..} => data, // Ya está en la última versión }}
Comparación con Structs
Sección titulada «Comparación con Structs»Característica | Structs | Enums |
---|---|---|
Estructura de datos | Fija | Múltiples variantes |
Construcción | Un constructor | Múltiples constructores |
Emparejamiento | Deconstrucción directa | Requiere match |
Uso de memoria | Tamaño fijo | Tamaño variable |
Caso de uso | Datos estructurados | Datos variantes |
Limitaciones y Consideraciones
Sección titulada «Limitaciones y Consideraciones»Limitaciones Actuales
Sección titulada «Limitaciones Actuales»- Aliasing de variantes: Los alias
use
para variantes enum no están soportados actualmente - Reflexión de tipos: Información de tipo limitada en tiempo de ejecución
- Serialización: Consideraciones especiales para serialización/deserialización
Mejores Prácticas
Sección titulada «Mejores Prácticas»- Nombres descriptivos: Usa nombres de variantes claros y descriptivos
// ✅ Buenoenum PaymentMethod { CreditCard{number: String, expiry: String}, BankTransfer{account: String, routing: String}, DigitalWallet{address: String}}
// ❌ Maloenum PaymentMethod { A{data: String}, B{info: String}, C{addr: String}}
- Manejo exhaustivo: Siempre maneja todas las variantes en match
// ✅ Bueno - exhaustivomatch (status) { Status::Active => handle_active(), Status::Inactive => handle_inactive(), Status::Pending => handle_pending(),}
// ❌ Malo - no exhaustivo (compilará error)match (status) { Status::Active => handle_active(), Status::Inactive => handle_inactive(), // Falta Status::Pending}
- Campos significativos: Incluye solo campos necesarios en cada variante
// ✅ Buenoenum Event { UserLogin{user_id: u64, timestamp: u64}, UserLogout{user_id: u64, timestamp: u64}, AdminAction{admin_id: u64, action: String, timestamp: u64}}
// ❌ Malo - campos innecesariosenum Event { UserLogin{user_id: u64, admin_id: u64, timestamp: u64}, // admin_id no relevante UserLogout{user_id: u64, admin_id: u64, timestamp: u64}, AdminAction{user_id: u64, admin_id: u64, action: String, timestamp: u64} // user_id no relevante}
Patrones Avanzados
Sección titulada «Patrones Avanzados»Enums Anidados
Sección titulada «Enums Anidados»enum Response<T> { Success{data: T}, Error{code: ErrorCode}}
enum ErrorCode { InvalidInput{field: String}, Unauthorized, ServerError{message: String}}
Transformación de Enums
Sección titulada «Transformación de Enums»fun map_result<T, U, E>(result: Result<T, E>, f: |T| U): Result<U, E> { match (result) { Result::Ok(value) => Result::Ok(f(value)), Result::Err(error) => Result::Err(error), }}
Combinación de Enums
Sección titulada «Combinación de Enums»fun combine_options<T, U>(opt1: Option<T>, opt2: Option<U>): Option<(T, U)> { match (opt1) { Option::Some(val1) => match (opt2) { Option::Some(val2) => Option::Some((val1, val2)), Option::None => Option::None, }, Option::None => Option::None, }}
Conclusión
Sección titulada «Conclusión»Los tipos enum en Move proporcionan una manera poderosa de modelar datos que pueden tomar una de varias formas. Son especialmente útiles para:
- Manejar casos de error y éxito
- Representar estados de máquinas de estado
- Versionar estructuras de datos
- Modelar tipos de datos algebraicos
Al usar enums efectivamente, puedes crear código más expresivo, seguro y mantenible que capture las invariantes de dominio de tu aplicación directamente en el sistema de tipos.
Los enums, junto con el emparejamiento de patrones, proporcionan una alternativa elegante a los enfoques tradicionales de ramificación condicional y hacen que el código sea más resistente a errores relacionados con casos no manejados.