Error Set em Zig — O que é e Como Usar

Error Set em Zig — O que é e Como Usar

Definição

Um Error Set em Zig é um tipo que define um conjunto finito de erros nomeados que uma função pode retornar. É a parte “erro” de um Error Union. Error sets permitem que o sistema de tipos rastreie exatamente quais erros podem ocorrer em cada ponto do programa.

Error sets são representados internamente como inteiros, tornando-os extremamente eficientes — comparáveis a enums em termos de performance.

Por que Error Sets Importam

  1. Documentação no tipo: A assinatura da função declara exatamente quais erros pode produzir.
  2. Exaustividade: O compilador pode verificar se todos os casos de erro foram tratados em um switch.
  3. Composição: Error sets podem ser unidos com o operador ||, combinando erros de diferentes fontes.
  4. Eficiência: Representados como inteiros compactos, sem alocação.

Exemplo Prático

Declarando um Error Set

const ArquivoError = error{
    ArquivoNaoEncontrado,
    PermissaoNegada,
    DiskCheio,
};

fn abrirConfiguracao() ArquivoError![]const u8 {
    // Pode retornar qualquer erro do ArquivoError
    return ArquivoError.ArquivoNaoEncontrado;
}

Combinando Error Sets

const IoError = error{
    Timeout,
    ConexaoRecusada,
};

const AppError = error{
    ConfigInvalida,
    VersaoIncompativel,
};

// União de error sets
const TodosOsErros = IoError || AppError;

fn inicializar() TodosOsErros!void {
    // Pode retornar erros de ambos os conjuntos
}

Switch Exaustivo em Erros

const std = @import("std");

const ParseError = error{
    CaractereInvalido,
    FormatoIncorreto,
    NumeroGrande,
};

fn parsear(input: []const u8) ParseError!u32 {
    _ = input;
    return ParseError.FormatoIncorreto;
}

pub fn main() void {
    const resultado = parsear("abc");
    if (resultado) |valor| {
        std.debug.print("Valor: {}\n", .{valor});
    } else |err| switch (err) {
        ParseError.CaractereInvalido => std.debug.print("Caractere inválido\n", .{}),
        ParseError.FormatoIncorreto => std.debug.print("Formato incorreto\n", .{}),
        ParseError.NumeroGrande => std.debug.print("Número grande demais\n", .{}),
    }
}

Error Set Inferido

Quando você omite o error set, o compilador o infere automaticamente:

// O compilador calcula todos os erros possíveis
fn processar() !void {
    try etapa1();  // Erros de etapa1
    try etapa2();  // + Erros de etapa2
    try etapa3();  // + Erros de etapa3
    // Error set final = união de todos
}

anyerror

O tipo anyerror é o superconjunto de todos os error sets. Qualquer erro pode ser convertido para anyerror:

fn logErro(err: anyerror) void {
    std.debug.print("Erro: {s}\n", .{@errorName(err)});
}

Armadilhas Comuns

  • Usar anyerror em APIs públicas: Perde-se a documentação de quais erros são possíveis. Prefira error sets explícitos.
  • Error sets muito grandes: Se sua função pode retornar dezenas de erros diferentes, talvez seja hora de refatorar.
  • Esquecer que error sets são tipos: Você pode usar @TypeOf, @errorName e outras built-ins para introspecção.
  • Confundir com enums: Error sets parecem enums, mas são um sistema à parte com semântica específica.

Termos Relacionados

  • Error Union — Tipo que combina error set com payload
  • Try — Propagação automática de erros
  • Catch — Captura e tratamento de erros
  • Errdefer — Limpeza condicional em caso de erro

Error Sets em APIs Públicas

Ao projetar uma biblioteca, usar error sets explícitos em vez de anyerror é uma das formas mais eficazes de documentar o comportamento esperado. O tipo de retorno torna-se parte da interface da API:

/// Erros que podem ocorrer ao conectar ao banco de dados
pub const ConexaoError = error{
    HostNaoEncontrado,
    TimeoutDeConexao,
    AutenticacaoFalhou,
    VersaoIncompativel,
};

/// Erros que podem ocorrer em queries
pub const QueryError = error{
    SyntaxeInvalida,
    TabelaNaoExiste,
    PermissaoNegada,
    ResultadoVazio,
};

/// Todos os erros do módulo de banco
pub const DatabaseError = ConexaoError || QueryError;

pub fn conectar(host: []const u8) ConexaoError!Conexao {
    _ = host;
    return error.HostNaoEncontrado;
}

pub fn query(conn: Conexao, sql: []const u8) QueryError!Resultado {
    _ = conn;
    _ = sql;
    return error.SyntaxeInvalida;
}

Introspecção de Error Sets

O Zig oferece built-ins para trabalhar com error sets em comptime:

const std = @import("std");

const MeuError = error{
    Overflow,
    DivisaoPorZero,
    ValorInvalido,
};

// Obter o nome de um erro como string
pub fn nomeDoErro(err: MeuError) []const u8 {
    return @errorName(err);
}

// Obter o valor inteiro de um erro
pub fn idDoErro(err: MeuError) u16 {
    return @intFromError(err);
}

test "introspecção de erros" {
    const err = MeuError.Overflow;
    try std.testing.expectEqualStrings("Overflow", @errorName(err));
}

Comparação com Outras Linguagens

Em Rust, erros são tipos que implementam a trait Error, geralmente definidos como enums. Em Go, erros são valores da interface error. Em C++, exceções são objetos de qualquer tipo derivado de std::exception. A abordagem do Zig com error sets é mais próxima de Rust (enums de erros tipados), mas com a vantagem de que a união de error sets e a inferência automática reduzem a quantidade de código boilerplate necessário.

A principal diferença em relação a exceções (Java, C++, Python) é que em Zig os erros são valores — eles não têm custo em tempo de execução quando não ocorrem e não têm stack unwinding automático.

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.