Catch em Zig — O que é e Como Usar
Definição
O operador catch em Zig é usado para capturar e tratar erros de error unions (ErrorSet!T). Ele fornece um mecanismo para reagir quando uma operação falha, seja fornecendo um valor de fallback, executando lógica de recuperação ou propagando o erro de forma diferente.
A sintaxe básica é: error_union catch fallback ou error_union catch |err| { ... }.
Por que Catch Importa
- Tratamento local de erros: Permite lidar com o erro no ponto exato onde ele ocorre.
- Valores de fallback: Fornece alternativas quando operações falham.
- Captura do erro: Permite inspecionar qual erro ocorreu para tomar decisões.
- Complementa
try: Enquantotrypropaga,catchtrata.
Exemplo Prático
Catch com Valor de Fallback
const std = @import("std");
fn parsearNumero(texto: []const u8) !u32 {
return std.fmt.parseInt(u32, texto, 10);
}
pub fn main() void {
// Se falhar, usa 0 como padrão
const valor = parsearNumero("42") catch 0;
std.debug.print("Valor: {}\n", .{valor}); // 42
const invalido = parsearNumero("abc") catch 0;
std.debug.print("Inválido: {}\n", .{invalido}); // 0
}
Catch com Captura de Erro
const std = @import("std");
fn abrirArquivo(caminho: []const u8) !std.fs.File {
return std.fs.cwd().openFile(caminho, .{});
}
pub fn main() void {
const arquivo = abrirArquivo("config.txt") catch |err| {
switch (err) {
error.FileNotFound => {
std.debug.print("Arquivo não encontrado, usando padrões\n", .{});
return;
},
error.AccessDenied => {
std.debug.print("Permissão negada\n", .{});
return;
},
else => {
std.debug.print("Erro inesperado: {}\n", .{err});
return;
},
}
};
defer arquivo.close();
std.debug.print("Arquivo aberto com sucesso\n", .{});
}
Catch com Bloco e Lógica
const porta = parsearNumero(config.porta_texto) catch blk: {
std.log.warn("Porta inválida, usando 8080", .{});
metricas.incrementar("config_fallback");
break :blk 8080;
};
Catch vs Try
fn processar() !void {
// try: propaga o erro para o chamador
const a = try operacao1();
// catch: trata o erro localmente
const b = operacao2() catch |err| {
std.log.err("operacao2 falhou: {}", .{err});
return err; // Pode re-propagar manualmente
};
_ = a;
_ = b;
}
Catch com Unreachable
// Quando sabemos que o erro é impossível
const valor: u32 = std.fmt.parseInt(u32, "42", 10) catch unreachable;
Padrão: Logging + Fallback
fn obterConfiguracao() Config {
return lerConfigDoArquivo() catch |err| {
std.log.warn("Falha ao ler config ({s}), usando padrão", .{@errorName(err)});
return Config.padrao();
};
}
Catch vs Orelse
| Operador | Funciona com | Trata |
|---|---|---|
catch | !T (error union) | Erros |
orelse | ?T (optional) | null |
// catch para error unions
const a: anyerror!u32 = error.Falha;
const val_a = a catch 0;
// orelse para optionals
const b: ?u32 = null;
const val_b = b orelse 0;
Armadilhas Comuns
- Usar
catchcom optional:catchfunciona apenas com error unions (!T). Para optionals (?T), useorelse. - Catch com unreachable irresponsável: Se o erro realmente ocorrer em Release, é comportamento indefinido. Use apenas quando tiver certeza.
- Engolir erros silenciosamente:
operacao() catch {}ignora o erro sem tratamento. Pelo menos registre um log. - Não usar a informação do erro: Quando possível, capture o erro com
catch |err|para logging ou decisões.
Boas Práticas com Catch
O uso adequado de catch melhora a robustez e legibilidade do código. Algumas recomendações:
1. Prefira catch |err| a catch simples: Capturar o erro permite logging e diagnóstico mais detalhado.
2. Evite engolir erros: operacao() catch {} é tentador, mas silencia problemas reais. Pelo menos registre o erro:
operacao() catch |err| {
std.log.warn("operacao falhou: {s}", .{@errorName(err)});
};
3. Use catch unreachable com critério: É seguro apenas quando a análise lógica do código garante que o erro é impossível naquele ponto. Em ReleaseFast, isso vira comportamento indefinido se você estiver errado.
4. Combine catch com métricas: Em serviços de produção, o bloco catch é o lugar certo para incrementar contadores de erro antes de retornar um fallback.
const config = lerConfig() catch |err| blk: {
metricas.contarErro("config_load", err);
std.log.err("Config falhou ({s}), usando padrão", .{@errorName(err)});
break :blk Config.padrao();
};
Comparação com Outras Linguagens
Em linguagens com exceções (Java, C++, Python), o equivalente seria try/catch. A diferença fundamental é que em Zig os erros são valores no sistema de tipos — não há overhead de stack unwinding, e o compilador pode garantir que todos os erros são tratados. Em Rust, o equivalente é o método .unwrap_or() e o operador ? (semelhante ao try do Zig). A vantagem do catch do Zig sobre o match do Rust é a sintaxe mais concisa para o caso comum de fornecer um valor de fallback.
Termos Relacionados
- Error Union — Tipo que pode conter erro ou valor
- Try — Propagação automática de erros
- Orelse — Equivalente para optionals
- Error Set — Conjuntos de erros nomeados
- Errdefer — Limpeza condicional em caso de erro