Error Set em Zig — O que é e Como Usar
Definição
Um Error Set em Zig é um tipo que define um conjunto finito de erros nomeados que uma função pode retornar. É a parte “erro” de um Error Union. Error sets permitem que o sistema de tipos rastreie exatamente quais erros podem ocorrer em cada ponto do programa.
Error sets são representados internamente como inteiros, tornando-os extremamente eficientes — comparáveis a enums em termos de performance.
Por que Error Sets Importam
- Documentação no tipo: A assinatura da função declara exatamente quais erros pode produzir.
- Exaustividade: O compilador pode verificar se todos os casos de erro foram tratados em um
switch. - Composição: Error sets podem ser unidos com o operador
||, combinando erros de diferentes fontes. - Eficiência: Representados como inteiros compactos, sem alocação.
Exemplo Prático
Declarando um Error Set
const ArquivoError = error{
ArquivoNaoEncontrado,
PermissaoNegada,
DiskCheio,
};
fn abrirConfiguracao() ArquivoError![]const u8 {
// Pode retornar qualquer erro do ArquivoError
return ArquivoError.ArquivoNaoEncontrado;
}
Combinando Error Sets
const IoError = error{
Timeout,
ConexaoRecusada,
};
const AppError = error{
ConfigInvalida,
VersaoIncompativel,
};
// União de error sets
const TodosOsErros = IoError || AppError;
fn inicializar() TodosOsErros!void {
// Pode retornar erros de ambos os conjuntos
}
Switch Exaustivo em Erros
const std = @import("std");
const ParseError = error{
CaractereInvalido,
FormatoIncorreto,
NumeroGrande,
};
fn parsear(input: []const u8) ParseError!u32 {
_ = input;
return ParseError.FormatoIncorreto;
}
pub fn main() void {
const resultado = parsear("abc");
if (resultado) |valor| {
std.debug.print("Valor: {}\n", .{valor});
} else |err| switch (err) {
ParseError.CaractereInvalido => std.debug.print("Caractere inválido\n", .{}),
ParseError.FormatoIncorreto => std.debug.print("Formato incorreto\n", .{}),
ParseError.NumeroGrande => std.debug.print("Número grande demais\n", .{}),
}
}
Error Set Inferido
Quando você omite o error set, o compilador o infere automaticamente:
// O compilador calcula todos os erros possíveis
fn processar() !void {
try etapa1(); // Erros de etapa1
try etapa2(); // + Erros de etapa2
try etapa3(); // + Erros de etapa3
// Error set final = união de todos
}
anyerror
O tipo anyerror é o superconjunto de todos os error sets. Qualquer erro pode ser convertido para anyerror:
fn logErro(err: anyerror) void {
std.debug.print("Erro: {s}\n", .{@errorName(err)});
}
Armadilhas Comuns
- Usar
anyerrorem APIs públicas: Perde-se a documentação de quais erros são possíveis. Prefira error sets explícitos. - Error sets muito grandes: Se sua função pode retornar dezenas de erros diferentes, talvez seja hora de refatorar.
- Esquecer que error sets são tipos: Você pode usar
@TypeOf,@errorNamee outras built-ins para introspecção. - Confundir com enums: Error sets parecem enums, mas são um sistema à parte com semântica específica.
Termos Relacionados
- Error Union — Tipo que combina error set com payload
- Try — Propagação automática de erros
- Catch — Captura e tratamento de erros
- Errdefer — Limpeza condicional em caso de erro
Error Sets em APIs Públicas
Ao projetar uma biblioteca, usar error sets explícitos em vez de anyerror é uma das formas mais eficazes de documentar o comportamento esperado. O tipo de retorno torna-se parte da interface da API:
/// Erros que podem ocorrer ao conectar ao banco de dados
pub const ConexaoError = error{
HostNaoEncontrado,
TimeoutDeConexao,
AutenticacaoFalhou,
VersaoIncompativel,
};
/// Erros que podem ocorrer em queries
pub const QueryError = error{
SyntaxeInvalida,
TabelaNaoExiste,
PermissaoNegada,
ResultadoVazio,
};
/// Todos os erros do módulo de banco
pub const DatabaseError = ConexaoError || QueryError;
pub fn conectar(host: []const u8) ConexaoError!Conexao {
_ = host;
return error.HostNaoEncontrado;
}
pub fn query(conn: Conexao, sql: []const u8) QueryError!Resultado {
_ = conn;
_ = sql;
return error.SyntaxeInvalida;
}
Introspecção de Error Sets
O Zig oferece built-ins para trabalhar com error sets em comptime:
const std = @import("std");
const MeuError = error{
Overflow,
DivisaoPorZero,
ValorInvalido,
};
// Obter o nome de um erro como string
pub fn nomeDoErro(err: MeuError) []const u8 {
return @errorName(err);
}
// Obter o valor inteiro de um erro
pub fn idDoErro(err: MeuError) u16 {
return @intFromError(err);
}
test "introspecção de erros" {
const err = MeuError.Overflow;
try std.testing.expectEqualStrings("Overflow", @errorName(err));
}
Comparação com Outras Linguagens
Em Rust, erros são tipos que implementam a trait Error, geralmente definidos como enums. Em Go, erros são valores da interface error. Em C++, exceções são objetos de qualquer tipo derivado de std::exception. A abordagem do Zig com error sets é mais próxima de Rust (enums de erros tipados), mas com a vantagem de que a união de error sets e a inferência automática reduzem a quantidade de código boilerplate necessário.
A principal diferença em relação a exceções (Java, C++, Python) é que em Zig os erros são valores — eles não têm custo em tempo de execução quando não ocorrem e não têm stack unwinding automático.