Tipos Enumeración (enum
)
En C#, un enum
es un tipo de valor que asigna nombres simbólicos a valores
enteros:
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
Rust tiene una sintaxis prácticamente idéntica para hacer lo mismo:
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
A diferencia de en .NET, una instancia de un tipo enum
en Rust no tiene ningún
comportamiento predefinido que se herede. Ni siquiera puede participar en
comprobaciones de igualdad tan simples como dow == DayOfWeek::Friday
. Para
hacerlo en cierta medida comparable en función con un enum
en C#, utiliza
el atributo #derive
para que los macros implementen automáticamente
la funcionalidad comúnmente necesaria:
#[derive(Debug, // habilita el formateo en "{:?}"
Clone, // requerido por Copy
Copy, // habilita la semántica de copia por valor
Hash, // habilita la posibilidad de usar en tipos de mapa
PartialEq // habilita la igualdad de valores (==)
)]
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
}
fn main() {
let dow = DayOfWeek::Wednesday;
println!("Day of week = {dow:?}");
if dow == DayOfWeek::Friday {
println!("Yay! It's the weekend!");
}
// coerce to integer
let dow = dow as i32;
println!("Day of week = {dow:?}");
let dow = dow as DayOfWeek;
println!("Day of week = {dow:?}");
}
Como muestra el ejemplo anterior, un enum
puede ser convertido a su valor
integral asignado, pero lo contrario no es posible como en C# (aunque esto a
veces tiene la desventaja en C#/.NET de que una instancia de enum
puede
contener un valor no representado). En su lugar, depende del desarrollador
proporcionar una función auxiliar de este tipo:
impl DayOfWeek {
fn from_i32(n: i32) -> Result<DayOfWeek, i32> {
use DayOfWeek::*;
match n {
0 => Ok(Sunday),
1 => Ok(Monday),
2 => Ok(Tuesday),
3 => Ok(Wednesday),
4 => Ok(Thursday),
5 => Ok(Friday),
6 => Ok(Saturday),
_ => Err(n)
}
}
}
La función from_i32
devuelve un DayOfWeek
en un Result
indicando éxito
(Ok
) si n
es válido. De lo contrario, devuelve n
tal cual en un Result
que indica fallo (Err
):
let dow = DayOfWeek::from_i32(5);
println!("{dow:?}"); // prints: Ok(Friday)
let dow = DayOfWeek::from_i32(50);
println!("{dow:?}"); // prints: Err(50)
Existen crates en Rust que pueden ayudar a implementar este mapeo a partir de tipos integrales en lugar de tener que codificarlos manualmente.
Un tipo enum
en Rust también puede servir como una forma de diseñar tipos de
unión (discriminados), que permiten que diferentes variantes contengan datos
específicos para cada variante.
Por ejemplo:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
Esta forma de declaración de enum
no existe en C#, pero se puede emular con
registros (class records):
var home = new IpAddr.V4(127, 0, 0, 1);
var loopback = new IpAddr.V6("::1");
abstract record IpAddr
{
public sealed record V4(byte A, byte B, byte C, byte D): IpAddr;
public sealed record V6(string Address): IpAddr;
}
La diferencia entre ambas es que la definición en Rust produce un
tipo cerrado sobre las variantes. En otras palabras, el compilador sabe que
no habrá otras variantes de IpAddr
excepto IpAddr::V4
y IpAddr::V6
, y
puede utilizar ese conocimiento para realizar verificaciones más estrictas.
Por ejemplo, en una expresión match
que es similar a la expresión switch
en
C#, el compilador de Rust generará un error a menos que se cubran todas las
variantes. En cambio, la emulación con C# crea realmente una jerarquía de
clases (aunque expresada de manera muy concisa) y, dado que IpAddr
es una
clase base abstracta, el conjunto de todos los tipos que puede representar es
desconocido para el compilador.