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ção | Sintaxe | Descrição |
|---|---|---|
| Definir erros | error{ A, B } | Conjunto de erros |
| Error union | E!T ou !T | Tipo que pode ser erro |
| Retornar erro | return error.Nome | Produzir erro |
| Propagar | try expr | Propagar erro se houver |
| Valor padrão | expr catch val | Valor se erro |
| Tratar | expr catch |e| ... | Bloco de tratamento |
| Cleanup sempre | defer expr | Executa ao sair do escopo |
| Cleanup no erro | errdefer expr | Executa só se erro |
| Assert sem erro | expr catch unreachable | Panic se erro |
| Desempacotar | if (expr) |v| ... else |e| ... | Condição com erro |
Veja Também
- Controle de Fluxo — if, switch com erros
- Funções — Retorno de erros em funções
- Testing — Testes com erros
- Padrão Retry — Padrão de retentativa
- Padrão Circuit Breaker — Proteção contra falhas
- Troubleshooting: Crashes — Debug de erros em runtime