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:
- Un motor no arranca (EngineError),
- 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 }, |