Cheatsheet: Error Handling em Zig

Cheatsheet: Error Handling em Zig

O sistema de erros de Zig é uma das características mais distintas da linguagem. Em vez de exceções, Zig usa error unions que são verificados pelo compilador.

Conjuntos de Erros (Error Sets)

// Definir conjunto de erros
const FileError = error{
    ArquivoNaoEncontrado,
    PermissaoNegada,
    DiskCheio,
};

const NetworkError = error{
    Timeout,
    ConexaoRecusada,
    DnsNaoResolvido,
};

// Combinar conjuntos de erros
const AppError = FileError || NetworkError;

// anyerror — qualquer erro possível
fn funcaoGenerica() anyerror!void {
    // pode retornar qualquer tipo de erro
}

Error Unions

// Error union: tipo que pode ser erro ou valor
fn dividir(a: f64, b: f64) error{DivisaoPorZero}!f64 {
    if (b == 0) return error.DivisaoPorZero;
    return a / b;
}

// Com anyerror (inferido)
fn abrir(path: []const u8) !std.fs.File {
    return std.fs.cwd().openFile(path, .{});
}

// Retornar void com possível erro
fn salvar(dados: []const u8) !void {
    const file = try std.fs.cwd().createFile("output.txt", .{});
    defer file.close();
    try file.writeAll(dados);
}

try — Propagar Erros

// try desempacota o valor ou retorna o erro imediatamente
fn processar() !i32 {
    const arquivo = try abrirArquivo();  // propaga erro se falhar
    defer arquivo.close();
    const dados = try lerDados(arquivo);  // propaga erro se falhar
    return try parsear(dados);             // propaga erro se falhar
}

// Equivalente a:
fn processarExplicito() !i32 {
    const arquivo = abrirArquivo() catch |err| return err;
    defer arquivo.close();
    const dados = lerDados(arquivo) catch |err| return err;
    return parsear(dados) catch |err| return err;
}

catch — Tratar Erros

// catch com valor padrão
const valor = funcaoQuePoderFalhar() catch 0;

// catch com bloco
const valor2 = funcaoQuePoderFalhar() catch |err| blk: {
    std.log.err("Falha: {}\n", .{err});
    break :blk valorPadrao;
};

// catch com retorno
const valor3 = funcaoQuePoderFalhar() catch |err| {
    return err;  // propagar o erro
};

// catch unreachable (assert que não falha)
const valor4 = funcaoQuePoderFalhar() catch unreachable;

// catch com switch no erro
const resultado = operacao() catch |err| switch (err) {
    error.Timeout => {
        std.log.warn("Timeout, tentando novamente...\n", .{});
        return operacao() catch |e| return e;
    },
    error.ConexaoRecusada => {
        std.log.err("Servidor indisponível\n", .{});
        return err;
    },
    else => return err,
};
_ = resultado;

if com Error Unions

const resultado: anyerror!i32 = calcular();

// Desempacotar com if
if (resultado) |valor| {
    std.debug.print("Sucesso: {}\n", .{valor});
} else |err| {
    std.debug.print("Erro: {}\n", .{err});
}

// Com tipo de erro específico
if (resultado) |valor| {
    processar(valor);
} else |err| switch (err) {
    error.OutOfMemory => @panic("Sem memória"),
    error.InvalidInput => std.log.warn("Entrada inválida\n", .{}),
    else => return err,
}

defer e errdefer

fn abrirConexao() !Conexao {
    const socket = try criarSocket();
    // errdefer SÓ executa se a função retornar erro
    errdefer socket.close();

    try socket.connect(endereco);
    // Se connect falhar, socket.close() é chamado

    const sessao = try iniciarSessao(socket);
    errdefer sessao.fechar();

    try autenticar(sessao);
    // Se autenticar falhar, sessao.fechar() e socket.close() são chamados

    return .{ .socket = socket, .sessao = sessao };
    // Se tudo der certo, errdefers NÃO executam
}

// defer sempre executa ao sair do escopo
fn processarArquivo(path: []const u8) !void {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();  // SEMPRE fecha, erro ou não

    const allocator = std.heap.page_allocator;
    const data = try file.readToEndAlloc(allocator, 1024 * 1024);
    defer allocator.free(data);  // SEMPRE libera

    try processar(data);
}

// Ordem de execução: LIFO (último a entrar, primeiro a sair)
fn exemplo() void {
    defer std.debug.print("1\n", .{});
    defer std.debug.print("2\n", .{});
    defer std.debug.print("3\n", .{});
    // Imprime: 3, 2, 1
}

Erros em Loops

// Coletar resultados, ignorando erros
fn processarTodos(items: []const Item) void {
    for (items) |item| {
        processar(item) catch |err| {
            std.log.warn("Falha no item: {}\n", .{err});
            continue;
        };
    }
}

// Parar no primeiro erro
fn processarTodosOuFalhar(items: []const Item) !void {
    for (items) |item| {
        try processar(item);
    }
}

Padrão Retry

fn comRetry(comptime max_tentativas: u32, func: anytype, args: anytype) !ReturnType(@TypeOf(func)) {
    var tentativa: u32 = 0;
    while (tentativa < max_tentativas) : (tentativa += 1) {
        if (@call(.auto, func, args)) |resultado| {
            return resultado;
        } else |err| {
            if (tentativa == max_tentativas - 1) return err;
            std.time.sleep(std.time.ns_per_s * std.math.pow(u64, 2, tentativa));
        }
    }
    unreachable;
}

Erros Customizados com Contexto

const ParseError = struct {
    linha: usize,
    coluna: usize,
    mensagem: []const u8,
};

const Parser = struct {
    ultimo_erro: ?ParseError = null,

    pub fn parse(self: *Parser, input: []const u8) error{ParseFailed}!AST {
        // ... lógica de parse
        if (encontrou_erro) {
            self.ultimo_erro = .{
                .linha = linha_atual,
                .coluna = coluna_atual,
                .mensagem = "Token inesperado",
            };
            return error.ParseFailed;
        }
        // ...
    }
};

// Uso
var parser = Parser{};
const ast = parser.parse(codigo) catch {
    if (parser.ultimo_erro) |err| {
        std.log.err("Erro na linha {}, coluna {}: {s}\n", .{
            err.linha, err.coluna, err.mensagem,
        });
    }
    return;
};
_ = ast;

Erros em Funções de Teste

test "operação deve suceder" {
    const resultado = try operacao();
    try std.testing.expectEqual(@as(i32, 42), resultado);
}

test "operação deve falhar" {
    const resultado = operacao();
    try std.testing.expectError(error.InvalidInput, resultado);
}

test "com expectEqual de erros" {
    if (operacao()) |_| {
        return error.TestUnexpectedResult;
    } else |err| {
        try std.testing.expectEqual(error.NotFound, err);
    }
}

Tabela de Referência

OperaçãoSintaxeDescrição
Definir erroserror{ A, B }Conjunto de erros
Error unionE!T ou !TTipo que pode ser erro
Retornar erroreturn error.NomeProduzir erro
Propagartry exprPropagar erro se houver
Valor padrãoexpr catch valValor se erro
Tratarexpr catch |e| ...Bloco de tratamento
Cleanup sempredefer exprExecuta ao sair do escopo
Cleanup no erroerrdefer exprExecuta só se erro
Assert sem erroexpr catch unreachablePanic se erro
Desempacotarif (expr) |v| ... else |e| ...Condição com erro

Veja Também

Continue aprendendo Zig

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