Errores

Errores

En Zig, los errores en tiempo de ejecución se controlan definiendo unos valores específicos de esta manera:


const NombreTipoError = error {

     NombreError,

      …

     NombreErrorN,

}; 


En realidad hemos puesto un nombre a un conjunto de errores. En Zig, un error es un valor. Puede ser definido y procesado de forma muy parecida a como lo hacemos con los enum.  

Cuando queremos señalar que ha sucedido un error durante la ejecución - por alguna razón concreta - devolvemos un tipo de error específico. Este tipo de error puede estar definido por el propio lenguaje, venir en alguna librería externa, o haber sido declarado por nosotros.

Consideremos el caso de los errores de un motor de dron:

  // los errores posibles al intentar arrancar un motor

  const EngineError = error{

    Damaged,

    Autorepairing,

    NotConnected,

  };

Con EngineError definimos los fallos que puede darnos el motor de dron que queremos montar en el ala. Siempre podemos modificar EngineError, añadiendo o quitando errores, si no los hemos usado en el código.

Ahora, si una operación no puede llevarse a cabo como por ejemplo - el motor está demasiado dañado para arrancar - devolveremos el error EngineError.Damaged. De esta manera no perdemos el control ante los posibles errores: expresamos y tratamos lo que puede fallar, en lugar de ocultarlo. Los errores, igual que enum y struct, nos sirven para dar forma al modelo de entidades y relaciones que aparecen en nuestro código.

        Si consideramos ¿qué fallos puede dar el “ala”? No conocemos todas las respuestas ahora mismo, pero podemos declarar el error que parece obvio por ahora: que no tenga suficiente potencia para llevar el peso:

  // errores que devuelve el Ala

  const WingError = error{

    NotEnoughEngines,

  };

De momento, ponemos ese error que indica que no hay suficientes motores (operativos). Si más adelante detectamos otros posibles fallos, los añadiremos. Escribir software es un proceso iterativo: ajustamos y ampliamos conforme el sistema crece.

No tenemos mucho tiempo, hay que ensamblar el “ala” con el material que tenemos, antes de que descubran que nos hemos escapado. Ellos tienen la ley de su parte, hecha a su medida, pero en realidad, no tienen conocimiento profundo de cómo funciona nada. Se creen que nos han vencido y eso nos da cierta ventaja.

wing.zig

const std = @import("std");

const print = std.debug.print;

// errores posibles de motor

const EngineError = error{

    Damaged,

    Autorepairing,

    NotConnected,

};

// errores que devuelve el Ala

const WingError = error{

    NotEnoughEngines,

};

// unión de tipos de errores que

// hacen el vuelo imposible

const CriticalFailure = EngineError || WingError;

// posibles estados de un motor

const EngineStatus = enum(i8) {

    failure = -1,

    off = 0,

    on = 1,

    testing = 2,

    autorepairing = 3,

};

// motor como struct

const DroneEngine = struct {

    var n_total_failed: u8 = 0;

    const N_MAX_RPM: u32 = 8500;

    const N_MIN_HEALTH: u32 = 75;

    e_status: EngineStatus,

    n_health: u8,

    s_key: []const u8,

    n_rpm: u32,

    // el botón de encendido/ apagado del motor

    fn toggle(self: *DroneEngine) EngineError!void {

        self.e_status = sw: switch (self.e_status) {

            EngineStatus.off => try self.turn_on(),

            // si está encendido apagamos

            EngineStatus.on => {

                self.turn_off();

                break :sw self.e_status;

            },

            EngineStatus.testing => EngineStatus.off,

            EngineStatus.autorepairing => EngineStatus.off,

            EngineStatus.failure => EngineStatus.failure,

        };

    }

    // apagar corta el suministro de energía sin más

    fn turn_off(self: *DroneEngine) void {

        self.e_status = .off;

        self.n_rpm = 0;

    }

    // función que comprueba si se cumplen las condiciones

    // para poder encender el motor

    fn turn_on(self: *DroneEngine) EngineError!EngineStatus {

        if (self.n_health > DroneEngine.N_MIN_HEALTH) {

            // el motor enciende

            self.e_status = EngineStatus.on;

            self.n_rpm = DroneEngine.N_MAX_RPM * self.n_health / 100;

        } else if (self.n_health > 20) {

            // el motor intenta autorepararse

            self.e_status = EngineStatus.autorepairing;

            self.n_rpm = DroneEngine.N_MAX_RPM * self.n_health / 100;

            DroneEngine.n_total_failed += 1;

            return EngineError.Autorepairing;

        } else {

            // fallo

            self.e_status = EngineStatus.failure;

            self.n_rpm = 0;

            DroneEngine.n_total_failed += 1;

            return EngineError.Damaged;

        }

        return self.e_status;

    }

    fn set_rpm(self: *DroneEngine, n_rpm: u32) bool {

        if (self.e_status != EngineStatus.on and

            self.e_status != EngineStatus.testing) return false;

        self.n_rpm = if (n_rpm > DroneEngine.N_MAX_RPM)

            DroneEngine.N_MAX_RPM

        else if (n_rpm < 0) 0 else n_rpm;

        return true;

    }

    // método que formatea el struct y se invoca

    // automáticamente cuando lo imprimimos

    pub fn format(self: DroneEngine, writer: anytype) !void {

        try writer.print(

            \\Motor {s}:

            \\  * rpm: {}

            \\  * health: {}

            \\  * status: {s}

        , .{

            self.s_key,

            self.n_rpm,

            self.n_health,

            @tagName(self.e_status),

        });

    }

};

// todos los motores disponibles

const a_all_engines = [_]DroneEngine{

    DroneEngine{ .s_key = "E1", .n_health = 100, .n_rpm = 0, .e_status = .off },

    DroneEngine{ .s_key = "E2", .n_health = 95, .n_rpm = 0, .e_status = .off },

    DroneEngine{ .s_key = "E3", .n_health = 100, .n_rpm = 0, .e_status = .off },

    DroneEngine{ .s_key = "E6", .n_health = 0, .n_rpm = 0, .e_status = .off },

    DroneEngine{ .s_key = "E4", .n_health = 98, .n_rpm = 0, .e_status = .off },

    DroneEngine{ .s_key = "E5", .n_health = 60, .n_rpm = 0, .e_status = .off },

};

// sitios para montar los motores

const a_placeholders = .{DroneEngine{

    .s_key = "EMPTY",

    .n_health = 0,

    .n_rpm = 0,

    .e_status = .failure,

}} ** 4;

// Ala como struct

const Wing = struct {

    // todos los motores van a ser montados en sus sitios

    a_engines: [4]DroneEngine = a_placeholders,

    // montar los motores en el ala

    fn mount_engines(self: *Wing, a_engines2mount: *const [4]DroneEngine) void {

        for (&self.a_engines, 0..) |_, n_i| {

            self.a_engines[n_i] = a_engines2mount[n_i];

        }

    }

    // encender el ala puede resultar en error crítico

    // o no devolver nada -> !void

    fn turn_on(self: *Wing) CriticalFailure!void {

        // reseteamos los errores cuando encendemos el ala

        DroneEngine.n_total_failed = 0;

        // si hay algún fallo crítico

        // apagamos todos los motores previamente encendidos

        errdefer {

            for (&self.a_engines, 0..) |*o_engine, n_i| {

                if (o_engine.e_status == .on) {

                    o_engine.turn_off();

                    print("Turning off engine: {}\n", // reporta el apagado

                        .{n_i + 1});

                }

            }

        }

        for (&self.a_engines, 0..) |*o_engine, n_i| {

            if (o_engine.e_status == .off) {

                o_engine.toggle() catch |errx| {

                    print("At Engine Socket: {} ERR: {}\n", // reporta

                        .{ n_i + 1, errx });

                };

            }

        }

        // si algún motor falla devolvemos el error

        // el ala necesita los 4 motores para poder volar

        // con el peso del jugador y 1KR

        if (DroneEngine.n_total_failed > 0) 

          return WingError.NotEnoughEngines;

    }

    // imprimimos el estado de los motores

    fn show_state(self: *Wing) void {

        print("-------------------------------------------------\n", .{});

        for (&self.a_engines) |*o_engine| {

            print("{f}\n", .{o_engine});

        }

        print("\n{} fallos\n", .{DroneEngine.n_total_failed});

        print("-------------------------------------------------\n", .{});

    }

    // control de la velocidad de un motor

    fn set_rpm_by_id(self: *Wing, n_engine: usize, n_rpm: u32) void {

        const n_engine_ix = if (n_engine < 1)

            0

        else if (n_engine > self.a_engines.len)

            self.a_engines.len - 1

        else

            n_engine - 1;

        _ = &self.a_engines[n_engine_ix].set_rpm(n_rpm);

    }

};

// tenemos llamadas a funciones que devuelven errores

// pero las controlamos con if, si lo hicieramos con try

// habría que usar !void

pub fn main() void {

    // el ala vacía

    var o_wing = Wing{};

    //  mostrar el ala vacía

    o_wing.show_state();

    // montamos 4 de los motores disponibles

    o_wing.mount_engines(a_all_engines[0..4]);

    // mostramos el estado del ala

    o_wing.show_state();

    // encendemos el ala

    if (o_wing.turn_on()) |_| {

        // si no tenemos errores

        // probamos a ajustar la velocidad de un motor

        o_wing.set_rpm_by_id(2, 5000);

        // si hay error lo mostramos

    } else |err_wing| print("Wing ERROR: {}\n", .{err_wing});

    o_wing.show_state();

}

$ zig run wing.zig

...

At Engine Socket: 4 ERR: error.Damaged

Turning off engine: 1

Turning off engine: 2

Turning off engine: 3

Wing ERROR: error.NotEnoughEngines

-------------------------------------------------

Motor E1:

  * rpm: 0

  * health: 100

  * status: off

Motor E2:

  * rpm: 0

  * health: 95

  * status: off

Motor E3:

  * rpm: 0

  * health: 100

  * status: off

Motor E6:

  * rpm: 0

  * health: 0

  * status: failure

1 fallos

-------------------------------------------------

Como sabemos, en la playa hay seis motores quitados de los drones ARP1:

  // todos los motores disponibles

  const a_all_engines = [_]DroneEngine{

    DroneEngine{ .s_key = "E1", .n_health = 100, .n_rpm = 0,

    ...

Han sido montados en el “ala” los primeros cuatro ([0..4]) y el jugador ha intentado arrancar:

    // montamos 4 de los motores disponibles

    o_wing.mount_engines(a_all_engines[0..4]);

    // encendemos el ala

    if (o_wing.turn_on()) |_| {

Pero ha saltado un error de un motor y un error de “ala”:

...

-------------------------------------------------

At Engine Socket: 4 ERR: error.Damaged

Turning off engine: 1

Turning off engine: 2

Turning off engine: 3

Wing ERROR: error.NotEnoughEngines

-------------------------------------------------

...

Motor E6:

  * rpm: 0

  * health: 0

  * status: failure

1 fallos

-------------------------------------------------

Fusión de conjuntos de errores

El “ala” puede no despegar por dos razones:

  1. Un motor no arranca (EngineError),
  2. No hay suficientes motores operativos montados (WingError),

Ambos casos se tratan como un fallo crítico que hemos combinado en uno solo usando ||.

  // unión de tipos de errores que

  // hacen el vuelo imposible

  const CriticalFailure = EngineError || WingError;

De esta manera, nuestra función turn_on devuelve cualquier error posible, sea del motor o del ala. Nos preocupamos de si el vuelo es posible o no.

errdefer

Por suerte, el sistema ha apagado los motores previamente encendidos en cuanto se ha detectado un error. Esto se consigue usando errdefer - una palabra reservada, prima de defer. Si defer sirve para posponer la ejecución de un comando hasta el cierre del bloque actual,  errdefer hace lo mismo pero solo si la función ha devuelto un error:

  fn turn_on(self: *Wing) CriticalFailure!void {

    //...

    errdefer {

       // código que se ejecuta solo si turn_on devuelve error

    }

    //... if ...

    return WingError.NotEnoughEngines;

  }

Tanto errdefer como defer, ejecutan sus aplazamientos en orden LIFO (último en entrar,  primero en salir) - como una pila.

Arreglos posibles

¿Detectas el error?

Te dejo pensar un momento mirando ese log de arranque.

El jugador, cansadísimo como estaba, se ha hecho un lío y ha montado el motor E6 en el cuarto lugar y ese motor, como ya sabemos, está totalmente roto. No va a poder usarlo. ¿Cómo lo solucionarías?

Te doy una pista:

  const a_all_engines = [_]DroneEngine{

    ...

    // con un simple comentario podrías apartar este motor

    DroneEngine{ .s_key = "E6", .n_health = 0, .n_rpm = 0, .e_status = .off },


Structs
Unions
© 2025 Zen of Zig