Introdução
Zig usa error unions em vez de exceções. Uma função que pode falhar retorna !T (error union), e o chamador deve tratar o erro com try (propagar) ou catch (tratar). Este é um dos aspectos mais elegantes de Zig.
Para error sets customizados, veja Error Sets Customizados. Para cleanup, consulte Padrões Errdefer. Para o tutorial completo, veja Tratamento de Erros em Zig.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja o guia de instalação
- Conhecimento básico de Zig. Consulte a introdução ao Zig
Try: Propagar Erros
const std = @import("std");
fn lerArquivo(caminho: []const u8) ![]u8 {
// try propaga o erro para o chamador
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close();
var buffer: [4096]u8 = undefined;
const n = try arquivo.readAll(&buffer);
return buffer[0..n];
}
fn processarConfig() !Config {
const dados = try lerArquivo("config.txt");
return try parsearConfig(dados);
}
try é equivalente a:
const resultado = funcao() catch |err| return err;
Catch: Tratar Erros
Catch com Handler
const valor = funcao() catch |err| {
std.log.err("Falha: {}", .{err});
return;
};
Catch com Valor Padrão
const porta = std.fmt.parseInt(u16, porta_str, 10) catch 8080;
Catch com Return
const arquivo = std.fs.cwd().openFile("dados.bin", .{}) catch |err| {
std.debug.print("Erro ao abrir: {}\n", .{err});
return err;
};
defer arquivo.close();
Switch em Erros
const MeuErro = error{
ArquivoNaoEncontrado,
PermissaoNegada,
FormatoInvalido,
Timeout,
};
fn processar(caminho: []const u8) MeuErro!Resultado {
// ...
}
fn executar() void {
const resultado = processar("dados.txt") catch |err| switch (err) {
error.ArquivoNaoEncontrado => {
std.debug.print("Arquivo não encontrado\n", .{});
return;
},
error.PermissaoNegada => {
std.debug.print("Sem permissão\n", .{});
return;
},
error.FormatoInvalido => {
std.debug.print("Formato inválido\n", .{});
return;
},
error.Timeout => {
std.debug.print("Timeout\n", .{});
return;
},
};
_ = resultado;
}
Compor Funções com Erros
fn pipeline(allocator: std.mem.Allocator, caminho: []const u8) !Resultado {
const raw = try lerArquivo(allocator, caminho);
defer allocator.free(raw);
const parsed = try parsear(raw);
const validado = try validar(parsed);
const resultado = try transformar(allocator, validado);
return resultado;
}
Cada try propaga o erro se a função falhar. O defer garante cleanup do raw independentemente de onde o erro ocorra.
Erros em Loops
fn processarTodos(allocator: std.mem.Allocator, caminhos: []const []const u8) !void {
for (caminhos) |caminho| {
processar(allocator, caminho) catch |err| {
std.log.warn("Falha em {s}: {}", .{ caminho, err });
continue; // Continuar com o próximo
};
}
}
// Ou coletar resultados e erros
fn processarComResultados(
allocator: std.mem.Allocator,
caminhos: []const []const u8,
) !struct { sucessos: usize, falhas: usize } {
var sucessos: usize = 0;
var falhas: usize = 0;
for (caminhos) |caminho| {
if (processar(allocator, caminho)) |_| {
sucessos += 1;
} else |_| {
falhas += 1;
}
}
return .{ .sucessos = sucessos, .falhas = falhas };
}
Retries
fn comRetry(comptime max_tentativas: usize, func: anytype, args: anytype) !@typeInfo(@TypeOf(@call(.auto, func, args))).error_union.payload {
var tentativa: usize = 0;
while (tentativa < max_tentativas) : (tentativa += 1) {
if (@call(.auto, func, args)) |resultado| {
return resultado;
} else |err| {
if (tentativa + 1 < max_tentativas) {
std.time.sleep(std.time.ns_per_s * (tentativa + 1));
} else {
return err;
}
}
}
unreachable;
}
Error Union com Optional
// Função que pode retornar erro, valor, ou "não encontrado"
fn buscar(id: u32) !?Resultado {
if (id == 0) return error.IdInvalido; // Erro
if (id > 1000) return null; // Não encontrado
return Resultado{ .id = id }; // Sucesso
}
// Tratar os três casos
if (buscar(42)) |maybe_resultado| {
if (maybe_resultado) |resultado| {
std.debug.print("Encontrado: {}\n", .{resultado.id});
} else {
std.debug.print("Não encontrado\n", .{});
}
} else |err| {
std.debug.print("Erro: {}\n", .{err});
}
Testes com Erros
test "função retorna erro esperado" {
const resultado = dividir(10, 0);
try std.testing.expectError(error.DivisaoPorZero, resultado);
}
test "função retorna valor correto" {
const resultado = try dividir(10, 2);
try std.testing.expectEqual(@as(f64, 5.0), resultado);
}
Veja Testes Unitários e Test Expectations.
Conclusão
O sistema de erros de Zig é simples e poderoso: try propaga, catch trata, e o compilador garante que todo erro é considerado. Sem exceções invisíveis, sem stack unwinding, sem overhead de runtime.
Para padrões relacionados, veja Error Sets Customizados, Padrões Errdefer e Error Logging.