Enums
Los enums nos permiten definir tipos de datos con un conjunto limitado de valores, a los que asignamos un nombre.
const NombreDelEnum = enum {
nombre_del_valor,
…
nombre_del_valor_N,
};
En el caso de un motor, recién desensamblado de un dron, conectado a nuestros instrumentos de medida, podríamos definir dos estados principales: encendido y apagado.
enum_e_status.zig |
|
const std = @import("std"); const print = std.debug.print; const EngineStatus = enum { on, off, }; pub fn main() void { const e_status_1 = EngineStatus.off; print("Estado del motor 1: {}\n", .{e_status_1}); } |
|
$ zig run enum_e_status.zig |
|
Estado del motor 1: .off |
Si solo tenemos estos dos estados, nos bastaría con un bit para representarlos. Podemos especificar el tipo de entero que se va a usar para almacenar el valor del enum:
|
const EngineStatus = enum(u1) { on, off, }; |
Si definimos el tipo de entero, hay que tener en cuenta su rango:
|
const EngineStatus = enum(u1) { on, off, unknown, // <- este ya no cabe en un bit }; |
error: enumeration value '2' too large for type 'u1'
Los valores ordinales de los enums comienzan en 0. Podemos acceder a ellos usando:
|
@intFromEnum(EngineStatus.off) // -> 1 |
También podemos definir los valores de forma explícita:
|
const EngineStatus = enum(i8) { // i8 para poder incluir valores negativos on = 1, off = 0, failure = -1, }; |
Si no definimos un valor ordinal explícito después de haber definido otros a mano, Zig va a intentar asignarlo a partir del último valor definido:
|
const EngineStatus = enum(i8) { on = 1, off = 0, failure = -1, testing, // añadido sin asignar a mano }; |
Esto provocará un error ya que el compilador intentará asignar a testing el valor 0 y ese valor ya está ocupado por off:
error: enum tag value 0 already taken
testing,
^~~~~~~
Para arreglarlo basta con poner las definiciones en el orden correcto:
|
// los valores manuales en orden const EngineStatus = enum(i8) { failure = -1, off = 0, on = 1, testing,// añadido sin asignar a mano }; |
Podemos declarar variables y constantes dentro de los enum. Estas pertenecen al tipo en sí - su ámbito (scope) será global y su valor no estará vinculado a una instancia.
enum_2.zig |
|
const std = @import("std"); const print = std.debug.print; const EngineStatus = enum(u2) { // esta variable da el valor para el tipo // no para las instancias individuales var n_starts: u32 = 0; on, off, failure, }; pub fn main() void { const e_status1 = EngineStatus.on; // acceso global, cambia el dato del tipo EngineStatus.n_starts += 1; var e_status2 = EngineStatus.on; // acceso global EngineStatus.n_starts += 1; print("Estado del motor 1: {}\n", .{e_status1}); e_status2 = EngineStatus.off; print("Estado del motor 2: {}\n", .{e_status2}); print("Contador de arranques de motores: {}\n", .{EngineStatus.n_starts}); } |
|
$ zig run enum_2.zig |
|
Estado del motor 1: .on Estado del motor 2: .off Contador de arranques de motores: 2 |
Podemos definir funciones dentro de un enum. Cuando una función forma parte de un enum se llama método. No dependen de una instancia específica, salvo cuando reciben una instancia como parámetro (self).
enum_6engines.zig |
|
const std = @import("std"); const print = std.debug.print; const EngineStatus = enum(u3) { var n_starts: u32 = 0; var n_tests: u32 = 0; on, // 0 off, // 1 failure, // 2 testing, // 3 autorepairing, // 4 // actúa sobre la instancia al pasarle self como parámetro fn is_stopped(self: EngineStatus) bool { return (self == EngineStatus.off) or (self == EngineStatus.failure); } // vinculado a la instancia al pasarle self fn is_autorepairing(self: EngineStatus) bool { return (self == EngineStatus.autorepairing); } // sobre la instancia al pasarle self fn is_testing(self: EngineStatus) bool { return (self == EngineStatus.testing); } // vinculado al tipo EngineStatus al no pasarle self fn count_start() void { EngineStatus.n_starts += 1; } // vinculado a la instancia fn can_start(self: EngineStatus) bool { return switch (self) { EngineStatus.testing, EngineStatus.autorepairing, EngineStatus.failure => false, else => true, }; } }; pub fn main() void { // 6 motores de dron conectados var a_drone_engines: [6]EngineStatus = .{ EngineStatus.off, EngineStatus.off, EngineStatus.on, EngineStatus.testing, EngineStatus.autorepairing, EngineStatus.failure, }; print("Estado de los motores: \n", .{}); print("_______________________________\n", .{}); print(" #\t| Estado \n", .{}); // listamos los estados por referencia usando un puntero // de esa manera podemos intentar "encender" el motor si está apagado for (&a_drone_engines, 0..) |*e_engine, n_i| { // convertir string a enum if (std.meta.stringToEnum(EngineStatus, "off") == e_engine.*) { // ejecutar un método vinculado a la instancia if (e_engine.*.can_start()) { e_engine.* = EngineStatus.on; // ejecutar un método vinculado al tipo EngineStatus.count_start(); } } print("Motor {}\t| {} ", .{ n_i + 1, e_engine, }); // convertir enum a string y comparar cadenas con std.mem.eql if (std.mem.eql(u8, @tagName(e_engine.*), "autorepairing")) { print("\n\t Reparación automática iniciada", .{}); } if (std.mem.eql(u8, @tagName(e_engine.*), "testing")) { print("\n\t Realizando auto-diagnóstico", .{}); // modificando la variable del tipo directamente EngineStatus.n_tests += 1; } print("\n", .{}); } print("_______________________________\n", .{}); print("Se han producido {} encendidos.\n", .{EngineStatus.n_starts}); print("Se están haciendo {} tests.\n", .{EngineStatus.n_tests}); } |
|
$ zig run enum_6engines.zig |
|
Estado de los motores: _______________________________ # | Estado Motor 1 | .on Motor 2 | .on Motor 3 | .on Motor 4 | .testing Realizando auto-diagnóstico Motor 5 | .autorepairing Reparación automática iniciada Motor 6 | .failure _______________________________ Se han producido 2 encendidos. Se están haciendo 1 tests. |
Funciones interesantes para manejar los enums:
- std.meta.stringToEnum convierte un string en el enum equivalente
- @tagName convierte enum a string
- @intFromEnum convierte un enum a entero
- @enumFromInt convierte entero a enum
- std.meta.tags devuelve los valores de un enum como array
- std.meta.fields devuelve la información de un enum
enum_values.zig |
|
const std = @import("std"); const print = std.debug.print; const EngineStatus = enum(i8) { failure = -1, off = 0, on = 1, testing, // 2 autorepairing, // 3 }; pub fn main() void { // std.meta.fields - información de campos print("\nNúmero de estados: {any}\n", .{std.meta.fields(EngineStatus).len}); // std.meta.tags - todos los valores del enum como array print("Todos los estados posibles detectados:\n", .{}); for (std.meta.tags(EngineStatus)) |e_status| { print(" {} - \"{s}\" = {d}\n", // estado .{ e_status, @tagName(e_status), @intFromEnum(e_status), }); } } |
|
$ zig run enum_values.zig |
|
Número de estados: 5 Todos los estados posibles detectados: .failure - "failure" = -1 .off - "off" = 0 .on - "on" = 1 .testing - "testing" = 2 .autorepairing - "autorepairing" = 3 |