zig-error-sets-union-types

Errores en Zig: error sets y uniones !T

  • 4 min

Un error set en Zig es un conjunto explícito de errores que una función puede devolver. Si vienes de Java, Python o C#, quizá estés acostumbrado a las excepciones; Zig prefiere tratar los errores como valores visibles.

Si vienes de C o Go, ya conocerás los códigos de error o las tuplas (valor, error). Son explícitos, pero también pueden introducir comprobaciones repetitivas.

Zig toma un camino diferente: los errores son valores, con soporte directo en el sistema de tipos y la sintaxis.

En Zig, un error no es una clase ni un texto. Cada nombre se representa mediante un código entero; por defecto el conjunto global usa u16, aunque el límite de errores puede configurar una anchura distinta. El lenguaje impide ignorar accidentalmente una unión de error.

Qué es un conjunto de errores

Un conjunto de errores (error set) es muy similar a un enum. Es una lista de posibles nombres de errores que definimos nosotros o las librerías que usamos.

// Definimos un conjunto de errores posibles para una operación de archivo
const FileError = error{
    AccessDenied,
    FileNotFound,
    DiskFull,
    BrokenPipe,
};
Copied!

Lo interesante es que los Error Sets son flexibles. Un mismo nombre de error (AccessDenied) tiene el mismo valor numérico único en todo el programa, independientemente de dónde se defina.

Devolviendo errores

Para que una función pueda fallar, debe devolver un error de este conjunto. Usamos la palabra clave return con el prefijo error.Nombre.

fn abrirArchivo(nombre: []const u8) FileError {
    if (nombre.len == 0) {
        return error.FileNotFound; // Devolvemos el error
    }
    // ... lógica ...
    return error.AccessDenied;
}
Copied!

Espera… ¿y si la función tiene éxito y no falla? ¿Cómo devolvemos el valor “bueno”? Para eso existe el tipo !.

El tipo unión de error (!T)

En Zig, una función no puede devolver “A veces un Error y a veces un Entero”. El sistema de tipos es estricto.

Para solucionar esto, Zig introduce el Error Union Type, que se denota con el operador !.

La sintaxis E!T significa: “Esta función devuelve un error del conjunto E, O BIEN un valor de tipo T.

// Esta función puede devolver un FileError O un u32
fn leerNumero(fallo: bool) FileError!u32 {
    if (fallo) {
        return error.DiskFull; // Retorno la parte izquierda (!)
    }
    
    return 100; // Retorno la parte derecha (u32)
}
Copied!

Esto es conceptualmente similar al Result<T, E> de Rust o al Either de la programación funcional, pero integrado tan profundamente en la sintaxis que es invisible.

Inferencia de error sets con !

A menudo, escribir la lista completa de errores que una función puede devolver es tedioso, especialmente si nuestra función llama a otras diez funciones, cada una con sus propios errores.

Zig nos permite usar el Error Set Inferido. Simplemente ponemos ! antes del tipo de retorno, sin especificar el nombre del conjunto de errores.

// Zig calcula automáticamente qué errores pueden salir de aquí
fn funcionCompleja() !u32 {
    if (condicion) return error.UnError;
    if (otra) return error.OtroError;
    return 42;
}
Copied!

El compilador examina la función y crea el conjunto de errores por nosotros.

  • Ventaja: si añades un error, no tienes que editar manualmente el conjunto de la firma.
  • Uso: resulta cómodo en código de aplicación y funciones privadas. En una API pública puede convenir un conjunto explícito (FileError!u32) para documentar qué puede fallar.

Fusión de conjuntos de errores

Zig puede combinar varios conjuntos de errores automáticamente.

Si tienes una función que puede devolver error{A} y otra que devuelve error{B}, y creas una función que llama a ambas, el tipo de error resultante es la unión error{A, B}.

const ErrorRed = error{ Desconectado };
const ErrorDB = error{ TablaInexistente };

fn tareaMixta() !void {
    // Puede devolver ErrorRed
    try conectar(); 
    // Puede devolver ErrorDB
    try consultar(); 
}
// El tipo de retorno real de tareaMixta es error{Desconectado, TablaInexistente}!void
Copied!

anyerror: el tipo global

Existe un tipo especial llamado anyerror. Es el conjunto de todos los errores posibles en todo el programa.

fn funcionGenerica() anyerror!void { ... }
Copied!

Generalmente debemos evitar usar anyerror a menos que sea estrictamente necesario (por ejemplo, en punteros a funciones genéricas o interfaces), ya que perdemos información específica sobre qué puede fallar. La inferencia (!) es casi siempre mejor.