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:

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

Enums, Structs, Errores
Structs
© 2025 Zen of Zig