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
- Tratamento de Erros em Zig — Tutorial
- Clean Code em Zig — Boas práticas
- Checklist de Code Review — Review
- Erros Comuns em Zig — Guia de erros
- Test Patterns — Testando erros