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 try_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 try_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::try_from_i32(5);
println!("{dow:?}"); // prints: Ok(Friday)
let dow = DayOfWeek::try_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.