← Volver al listado de tecnologías
Manejo de Errores en Zig
Manejo de Errores
Error Sets
const std = @import("std");
// Definir conjunto de errores
const FileError = error{
NotFound,
PermissionDenied,
IoError,
};
const ParseError = error{
InvalidFormat,
OutOfRange,
UnexpectedToken,
};
// Combinar error sets
const AppError = FileError || ParseError;
fn ejemplo() AppError!void {
return AppError.NotFound;
}
Error Unions
const std = @import("std");
const MathError = error{
DivisionByZero,
Overflow,
Underflow,
};
fn dividir(a: i32, b: i32) MathError!i32 {
if (b == 0) return MathError.DivisionByZero;
return @divTrunc(a, b);
}
fn raizCuadrada(n: f64) error{NegativeNumber}!f64 {
if (n < 0) return error.NegativeNumber;
return @sqrt(n);
}
pub fn main() void {
// Usando catch para manejar error
const resultado = dividir(10, 2) catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};
std.debug.print("Resultado: {d}\n", .{resultado});
// catch con valor por defecto
const r2 = dividir(10, 0) catch 0;
std.debug.print("Con default: {d}\n", .{r2});
}
Try y Propagación
const std = @import("std");
const Error = error{
InvalidInput,
ProcessingError,
OutputError,
};
fn paso1(input: i32) Error!i32 {
if (input < 0) return Error.InvalidInput;
return input * 2;
}
fn paso2(input: i32) Error!i32 {
if (input > 1000) return Error.ProcessingError;
return input + 100;
}
fn paso3(input: i32) Error!i32 {
if (input == 0) return Error.OutputError;
return input;
}
// try propaga el error automáticamente
fn pipeline(input: i32) Error!i32 {
const r1 = try paso1(input);
const r2 = try paso2(r1);
const r3 = try paso3(r2);
return r3;
}
pub fn main() void {
if (pipeline(50)) |resultado| {
std.debug.print("Éxito: {d}\n", .{resultado});
} else |err| {
std.debug.print("Error en pipeline: {}\n", .{err});
}
}
Errdefer
const std = @import("std");
const Recurso = struct {
datos: []u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, size: usize) !Recurso {
const datos = try allocator.alloc(u8, size);
return Recurso{ .datos = datos, .allocator = allocator };
}
pub fn deinit(self: *Recurso) void {
self.allocator.free(self.datos);
}
};
fn crearRecursos(allocator: std.mem.Allocator) !struct { r1: Recurso, r2: Recurso } {
var r1 = try Recurso.init(allocator, 100);
errdefer r1.deinit(); // Se ejecuta solo si hay error después
var r2 = try Recurso.init(allocator, 200);
errdefer r2.deinit();
// Si algo falla aquí, r1 y r2 se limpian automáticamente
if (r1.datos.len + r2.datos.len > 250) {
return error.TooMuchMemory;
}
return .{ .r1 = r1, .r2 = r2 };
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var recursos = try crearRecursos(gpa.allocator());
defer recursos.r1.deinit();
defer recursos.r2.deinit();
std.debug.print("Recursos creados exitosamente\n", .{});
}
Patrones Comunes
const std = @import("std");
const ValidationError = error{
EmptyString,
TooLong,
InvalidCharacter,
};
fn validarNombre(nombre: []const u8) ValidationError!void {
if (nombre.len == 0) return ValidationError.EmptyString;
if (nombre.len > 50) return ValidationError.TooLong;
for (nombre) |c| {
if (!std.ascii.isAlphabetic(c) and c != ' ') {
return ValidationError.InvalidCharacter;
}
}
}
// Patrón: convertir error a optional
fn buscarSeguro(arr: []const i32, valor: i32) ?usize {
for (arr, 0..) |elem, i| {
if (elem == valor) return i;
}
return null;
}
// Patrón: unwrap con mensaje
fn unwrapConPanic(valor: anyerror!i32) i32 {
return valor catch |err| {
std.debug.panic("Error inesperado: {}", .{err});
};
}
// Patrón: retry
fn operacionConReintentos(intentos: u32) !i32 {
var i: u32 = 0;
while (i < intentos) : (i += 1) {
if (realizarOperacion()) |resultado| {
return resultado;
} else |_| {
std.time.sleep(100 * std.time.ns_per_ms);
}
}
return error.MaxRetriesExceeded;
}
fn realizarOperacion() !i32 {
// Simulación
return error.TemporaryFailure;
}
pub fn main() void {
// Validación
validarNombre("Juan") catch |err| {
std.debug.print("Nombre inválido: {}\n", .{err});
};
// Optional
const arr = [_]i32{ 1, 2, 3 };
if (buscarSeguro(&arr, 2)) |idx| {
std.debug.print("Encontrado en: {d}\n", .{idx});
}
}
Error Trace
const std = @import("std");
fn nivel3() !void {
return error.ErrorEnNivel3;
}
fn nivel2() !void {
try nivel3();
}
fn nivel1() !void {
try nivel2();
}
pub fn main() void {
nivel1() catch |err| {
std.debug.print("Error capturado: {}\n", .{err});
// En modo debug, puedes obtener el stack trace
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
};
}
Testing de Errores
const std = @import("std");
const testing = std.testing;
const ParseError = error{
InvalidNumber,
OutOfRange,
EmptyInput,
};
fn parsearNumero(input: []const u8) ParseError!i32 {
if (input.len == 0) return ParseError.EmptyInput;
var resultado: i32 = 0;
for (input) |c| {
if (c < '0' or c > '9') return ParseError.InvalidNumber;
resultado = resultado * 10 + @as(i32, c - '0');
if (resultado < 0) return ParseError.OutOfRange; // overflow
}
return resultado;
}
fn dividirSeguro(a: i32, b: i32) error{DivisionByZero}!i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
fn validarRango(n: i32, min: i32, max: i32) error{OutOfRange}!i32 {
if (n < min or n > max) return error.OutOfRange;
return n;
}
test "parsear número válido" {
const resultado = try parsearNumero("123");
try testing.expectEqual(@as(i32, 123), resultado);
}
test "parsear número cero" {
const resultado = try parsearNumero("0");
try testing.expectEqual(@as(i32, 0), resultado);
}
test "parsear input vacío retorna error" {
const resultado = parsearNumero("");
try testing.expectError(ParseError.EmptyInput, resultado);
}
test "parsear caracteres inválidos retorna error" {
try testing.expectError(ParseError.InvalidNumber, parsearNumero("12a3"));
try testing.expectError(ParseError.InvalidNumber, parsearNumero("abc"));
}
test "división exitosa" {
const resultado = try dividirSeguro(10, 2);
try testing.expectEqual(@as(i32, 5), resultado);
}
test "división por cero" {
try testing.expectError(error.DivisionByZero, dividirSeguro(10, 0));
}
test "validar rango exitoso" {
const resultado = try validarRango(50, 0, 100);
try testing.expectEqual(@as(i32, 50), resultado);
}
test "validar rango fuera de límites" {
try testing.expectError(error.OutOfRange, validarRango(-1, 0, 100));
try testing.expectError(error.OutOfRange, validarRango(101, 0, 100));
}
test "encadenar operaciones con try" {
const paso1 = try dividirSeguro(100, 2);
const paso2 = try validarRango(paso1, 0, 100);
try testing.expectEqual(@as(i32, 50), paso2);
}
test "catch con valor por defecto" {
const resultado = dividirSeguro(10, 0) catch -1;
try testing.expectEqual(@as(i32, -1), resultado);
}
test "if con error union" {
if (parsearNumero("42")) |valor| {
try testing.expectEqual(@as(i32, 42), valor);
} else |_| {
try testing.expect(false); // No debería llegar aquí
}
}
Ejercicios
- Implementa un parser de JSON simple que retorne errores descriptivos
- Crea una función que lea un archivo y maneje todos los posibles errores
- Implementa un validador de email con errores específicos para cada caso
Resumen
- Error sets definen tipos de errores posibles
trypropaga errores,catchlos manejaerrdefergarantiza limpieza en caso de errortesting.expectErrorverifica errores específicos- Los errores en Zig son explícitos y tipados