Boas Práticas de Error Handling em Zig

Boas Práticas de Error Handling em Zig

O sistema de erros de Zig é único: error unions combinam o valor de retorno com um conjunto de erros possíveis, forçando o chamador a tratar cada erro explicitamente. Este artigo apresenta boas práticas para escrever código robusto com o sistema de erros de Zig.

Error Unions: O Básico

const std = @import("std");

// Definir error set específico
const ArquivoError = error{
    NaoEncontrado,
    PermissaoNegada,
    DiskCheio,
    IOError,
};

// Retornar error union
fn lerArquivo(caminho: []const u8) ArquivoError![]const u8 {
    _ = caminho;
    return error.NaoEncontrado;
}

// Tratar erros com switch
fn processarArquivo(caminho: []const u8) void {
    const conteudo = lerArquivo(caminho) catch |err| {
        switch (err) {
            error.NaoEncontrado => std.log.warn("Arquivo não existe: {s}", .{caminho}),
            error.PermissaoNegada => std.log.err("Sem permissão: {s}", .{caminho}),
            else => std.log.err("Erro de I/O: {s}", .{caminho}),
        }
        return;
    };
    _ = conteudo;
}

errdefer: Limpeza em Caso de Erro

const Recurso = struct {
    buffer: []u8,
    arquivo: std.fs.File,
    conexao: std.net.Stream,

    pub fn init(allocator: std.mem.Allocator) !Recurso {
        const buffer = try allocator.alloc(u8, 4096);
        // Se o próximo passo falhar, liberar buffer
        errdefer allocator.free(buffer);

        const arquivo = try std.fs.cwd().openFile("config.txt", .{});
        // Se o próximo passo falhar, fechar arquivo
        errdefer arquivo.close();

        const endereco = std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080);
        const conexao = try std.net.tcpConnectToAddress(endereco);
        // Se chegou aqui, tudo deu certo — nenhum errdefer executa

        return .{
            .buffer = buffer,
            .arquivo = arquivo,
            .conexao = conexao,
        };
    }

    pub fn deinit(self: *Recurso, allocator: std.mem.Allocator) void {
        allocator.free(self.buffer);
        self.arquivo.close();
        self.conexao.close();
    }
};

Padrão: Converter Erros com Contexto

const AppError = error{
    ConfigInvalida,
    BancoDeDadosOffline,
    ServicoIndisponivel,
};

fn inicializarApp() AppError!void {
    carregarConfig() catch return error.ConfigInvalida;
    conectarBanco() catch return error.BancoDeDadosOffline;
    verificarServicos() catch return error.ServicoIndisponivel;
}

Padrão: Fornecer Valores Default

fn obterPorta() u16 {
    // Se a variável de ambiente não existe, usar default
    const porta_str = std.posix.getenv("PORT") orelse return 8080;

    return std.fmt.parseInt(u16, porta_str, 10) catch 8080;
}

fn obterConfig(allocator: std.mem.Allocator) Config {
    // Tentar carregar config do arquivo, senão usar defaults
    return Config.carregarDeArquivo(allocator, "config.json") catch Config{
        .porta = 8080,
        .host = "localhost",
        .log_level = .info,
    };
}

Padrão: Retry com Backoff

fn conectarComRetry(endereco: std.net.Address) !std.net.Stream {
    var tentativa: u32 = 0;
    while (tentativa < 5) : (tentativa += 1) {
        return std.net.tcpConnectToAddress(endereco) catch |err| {
            std.log.warn("Tentativa {d}/5 falhou: {}", .{ tentativa + 1, err });
            const delay = std.math.pow(u64, 2, tentativa) * 100;
            std.time.sleep(delay * std.time.ns_per_ms);
            continue;
        };
    }
    return error.ConexaoFalhou;
}

Anti-Padrões

// RUIM: ignorar erro silenciosamente
_ = funcaoQueRetornaErro() catch {};

// MELHOR: logar o erro mesmo que não possa tratar
funcaoQueRetornaErro() catch |err| {
    std.log.warn("Erro ignorado: {}", .{err});
};

// RUIM: unreachable sem justificativa
const valor = funcaoFalivel() catch unreachable;

// MELHOR: documentar por que é unreachable
// Sabemos que este allocator nunca falha (arena com memória reservada)
const valor = funcaoFalivel() catch unreachable;

Conclusão

O sistema de erros de Zig é mais explícito que exceções (Java, Python) e mais ergonômico que códigos de retorno (C). As boas práticas fundamentais são: definir error sets específicos, usar errdefer para limpeza, converter erros com contexto e nunca ignorar erros silenciosamente.

Conteúdo Relacionado

Continue aprendendo Zig

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