Error Union em Zig — O que é e Como Usar
Definição
Um Error Union em Zig é um tipo composto representado pelo operador ! que pode conter ou um valor válido ou um erro. É o mecanismo fundamental de tratamento de erros da linguagem, substituindo exceções (como em Java/Python) e códigos de retorno (como em C).
A sintaxe ErrorSet!T significa “este valor é do tipo T ou é um erro do conjunto ErrorSet”. Quando o error set é omitido (!T), o compilador infere o conjunto de erros automaticamente.
Por que Error Unions Importam
- Erros são valores: Em Zig, erros são cidadãos de primeira classe no sistema de tipos — não são exceções invisíveis.
- Impossível ignorar erros: O compilador obriga você a lidar com o caso de erro explicitamente.
- Zero custo: Error unions são implementados de forma eficiente, sem overhead de exceções.
- Composição: Funções que retornam error unions se compõem naturalmente com
try.
Exemplo Prático
Retornando Error Unions
const std = @import("std");
const MathError = error{
DivisaoPorZero,
Overflow,
};
fn dividir(a: i32, b: i32) MathError!i32 {
if (b == 0) return MathError.DivisaoPorZero;
return @divTrunc(a, b);
}
pub fn main() void {
// Tratando o erro explicitamente
const resultado = dividir(10, 3) catch |err| {
std.debug.print("Erro: {}\n", .{err});
return;
};
std.debug.print("10 / 3 = {}\n", .{resultado});
}
Usando com try
fn processarDados() !void {
const valor = try dividir(100, 5); // Propaga erro automaticamente
std.debug.print("Resultado: {}\n", .{valor});
}
try é equivalente a:
const valor = dividir(100, 5) catch |err| return err;
Usando com if
fn exemploIf() void {
if (dividir(10, 0)) |valor| {
std.debug.print("Sucesso: {}\n", .{valor});
} else |err| {
std.debug.print("Erro: {}\n", .{err});
}
}
Error Union com tipo inferido
// O compilador infere o error set automaticamente
fn lerArquivo(caminho: []const u8) ![]const u8 {
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close();
return try arquivo.readToEndAlloc(std.heap.page_allocator, 1024 * 1024);
}
Anatomia do Tipo
ErrorSet ! PayloadType
│ │
│ └── Tipo do valor em caso de sucesso
└── Conjunto de possíveis erros
Exemplos:
error{OutOfMemory}![]u8 — Pode ser OutOfMemory ou um slice de bytes
anyerror!void — Qualquer erro ou void (sem valor)
!u32 — Error set inferido, valor u32
Combinando Error Sets
Quando uma função chama outras que retornam diferentes error sets, o compilador infere a união automaticamente:
const IoError = error{ DiskFull, PermissionDenied };
const ParseError = error{ InvalidFormat, UnexpectedEof };
// O compilador infere o tipo como IoError || ParseError
fn lerEProcessar(caminho: []const u8) !u32 {
const conteudo = try lerArquivo(caminho); // retorna IoError![]u8
const numero = try parsear(conteudo); // retorna ParseError!u32
return numero;
}
Você também pode combinar error sets explicitamente com ||:
const ErrosCombinados = IoError || ParseError;
fn funcao() ErrosCombinados!void {
// ...
}
Error Union vs Outros Mecanismos de Erro
| Mecanismo | Linguagem | Pode ignorar? | Overhead | Informação |
|---|---|---|---|---|
Error Union !T | Zig | Não (compilador força) | Zero | Error set tipado |
Result<T,E> | Rust | Não (com warnings) | Zero | Tipo genérico |
| Exceções | Java, Python | Sim (silencioso) | Alto (stack unwinding) | Classe de exceção |
Código de retorno int | C | Sim (fácil de esquecer) | Zero | Apenas um inteiro |
errno global | C | Sim | Zero | Código global mágico |
A principal vantagem do error union sobre exceções é o custo zero: não há stack unwinding, não há heap allocation para o objeto de exceção, e o compilador garante que todos os erros sejam tratados.
Boas Práticas
- Prefira error sets específicos a
anyerror: Error sets nomeados documentam o contrato da função e permitem que o compilador verifique exaustividade em switches de erro. - Use
errdeferpara cleanup em caso de falha: Quando a função aloca recursos antes de um possível erro,errdefergarante que o cleanup ocorra apenas em caso de falha (ao contrário dedefer, que sempre executa). - Documente erros possíveis: Mesmo que o compilador infira o error set, listar os erros possíveis em comentários ajuda quem usa a API.
- Propague com
try, não comcatch unreachable: Se não souber tratar o erro, propague-o.catch unreachableé uma bomba-relógio se a premissa estiver errada.
Armadilhas Comuns
- Usar
anyerrordesnecessariamente: Prefira error sets específicos.anyerrordesabilita otimizações do compilador e dificulta a documentação da API. - Ignorar erros com
catch unreachable: Só use quando tiver certeza absoluta de que o erro não pode ocorrer. Caso contrário, isso causará um panic em runtime. - Confundir
!Tcom?T: Error union (!T) carrega informação de erro; optional (?T) apenas indica presença/ausência de valor. - Não propagar erros: Se sua função não sabe lidar com o erro, propague-o com
tryem vez de engolir silenciosamente.
Termos Relacionados
- Error Set — Conjuntos de erros nomeados
- Try — Operador de propagação de erros
- Catch — Operador para capturar erros
- Optional — Tipo que pode ser null
- Errdefer — Defer condicional para erros