Saltearse al contenido

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.

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)
}

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;

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
}

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"),
}
}

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
}
}

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,
}
}

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"),
}
}

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
}
}
}

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"),
}
}

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
}
}

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
}
}
CaracterísticaStructsEnums
Estructura de datosFijaMúltiples variantes
ConstrucciónUn constructorMúltiples constructores
EmparejamientoDeconstrucción directaRequiere match
Uso de memoriaTamaño fijoTamaño variable
Caso de usoDatos estructuradosDatos variantes
  1. Aliasing de variantes: Los alias use para variantes enum no están soportados actualmente
  2. Reflexión de tipos: Información de tipo limitada en tiempo de ejecución
  3. Serialización: Consideraciones especiales para serialización/deserialización
  1. Nombres descriptivos: Usa nombres de variantes claros y descriptivos
// ✅ Bueno
enum PaymentMethod {
CreditCard{number: String, expiry: String},
BankTransfer{account: String, routing: String},
DigitalWallet{address: String}
}
// ❌ Malo
enum PaymentMethod {
A{data: String},
B{info: String},
C{addr: String}
}
  1. Manejo exhaustivo: Siempre maneja todas las variantes en match
// ✅ Bueno - exhaustivo
match (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
}
  1. Campos significativos: Incluye solo campos necesarios en cada variante
// ✅ Bueno
enum Event {
UserLogin{user_id: u64, timestamp: u64},
UserLogout{user_id: u64, timestamp: u64},
AdminAction{admin_id: u64, action: String, timestamp: u64}
}
// ❌ Malo - campos innecesarios
enum 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
}
enum Response<T> {
Success{data: T},
Error{code: ErrorCode}
}
enum ErrorCode {
InvalidInput{field: String},
Unauthorized,
ServerError{message: String}
}
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),
}
}
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,
}
}

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.