Catch Reached Unreachable — Como Resolver em Zig
O Que Este Erro Significa
O panic reached unreachable ocorre em runtime quando uma expressão catch unreachable encontra um erro real. O programador declarou ao compilador que aquela operação “nunca falhará”, mas em tempo de execução ela falhou. Zig trata isso como uma violação de contrato e emite um panic imediato.
A mensagem típica:
thread 1 panic: reached unreachable
Esse padrão é similar a um assert que falhou. O catch unreachable é essencialmente uma promessa ao compilador de que o erro não acontecerá, e quando essa promessa é quebrada, o programa encerra.
Causas Comuns
1. Presunção Incorreta de que Função Não Falhará
const std = @import("std");
pub fn main() void {
// PANIC: arquivo pode não existir!
const file = std.fs.cwd().openFile("config.txt", .{}) catch unreachable;
defer file.close();
_ = file;
}
2. Alocação que Pode Falhar
const std = @import("std");
pub fn main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// PANIC: alocação PODE falhar com OutOfMemory
const dados = allocator.alloc(u8, 1_000_000) catch unreachable;
_ = dados;
}
3. Parsing com Input Não Validado
const std = @import("std");
pub fn main() void {
const input = "nao_e_numero";
// PANIC: input não é um número válido
const numero = std.fmt.parseInt(i32, input, 10) catch unreachable;
_ = numero;
}
4. Conversão de Tipo que Pode Falhar
const std = @import("std");
pub fn main() void {
const valor: i32 = -1;
// PANIC: não pode converter -1 para u32
const positivo = std.math.cast(u32, valor) orelse unreachable;
_ = positivo;
}
5. Operação de I/O Sem Garantia
const std = @import("std");
pub fn main() void {
const stdout = std.io.getStdOut().writer();
// PANIC: escrita pode falhar (pipe quebrado, disco cheio, etc.)
stdout.print("Olá mundo\n", .{}) catch unreachable;
}
Como Corrigir
Solucao 1: Usar try ao Invés de catch unreachable
const std = @import("std");
pub fn main() !void {
const file = try std.fs.cwd().openFile("config.txt", .{});
defer file.close();
// Erro é propagado para o chamador
}
Solucao 2: Tratar o Erro com catch
const std = @import("std");
pub fn main() void {
const file = std.fs.cwd().openFile("config.txt", .{}) catch |err| {
std.debug.print("Não foi possível abrir config: {}\n", .{err});
return;
};
defer file.close();
_ = file;
}
Solucao 3: Usar catch com Valor Padrão
const std = @import("std");
pub fn main() void {
const input = "nao_e_numero";
const numero = std.fmt.parseInt(i32, input, 10) catch 0;
std.debug.print("Número: {}\n", .{numero});
}
Solucao 4: Validar Antes de Usar unreachable
Se você realmente precisa de unreachable, valide as condições primeiro:
const std = @import("std");
pub fn main() void {
const input = "42";
// Valida primeiro
for (input) |c| {
if (c < '0' or c > '9') {
std.debug.print("Input inválido\n", .{});
return;
}
}
// Agora é seguro usar catch unreachable (input validado)
const numero = std.fmt.parseInt(i32, input, 10) catch unreachable;
std.debug.print("Número: {}\n", .{numero});
}
Solucao 5: Usar @panic com Mensagem Customizada
Se o erro realmente não deveria acontecer, ao menos dê uma mensagem clara:
const std = @import("std");
pub fn main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const dados = allocator.alloc(u8, 64) catch {
@panic("Falha crítica: não foi possível alocar 64 bytes");
};
_ = dados;
}
Solucao 6: Usar if com Error Union
const std = @import("std");
pub fn main() void {
if (std.fs.cwd().openFile("config.txt", .{})) |file| {
defer file.close();
std.debug.print("Arquivo aberto com sucesso\n", .{});
} else |err| {
std.debug.print("Erro: {}\n", .{err});
}
}
Quando catch unreachable É Aceitável
O catch unreachable é justificado apenas em situações onde o erro é matematicamente impossível:
const std = @import("std");
pub fn main() void {
// bufPrint com buffer grande o suficiente e formato fixo
// NÃO pode falhar — o buffer é definitivamente grande o suficiente
var buf: [100]u8 = undefined;
const texto = std.fmt.bufPrint(&buf, "valor: {d}", .{@as(u32, 42)}) catch unreachable;
std.debug.print("{s}\n", .{texto});
}
fn somaSegura(a: u7, b: u7) u8 {
// u7 max = 127, portanto 127 + 127 = 254 < 256 (max u8)
// Essa adição NUNCA pode falhar com overflow em u8
return std.math.add(u8, a, b) catch unreachable;
}
Diferenca entre catch unreachable e orelse unreachable
// catch unreachable — para error unions (!T)
const valor = funcaoQuePodeFalhar() catch unreachable;
// orelse unreachable — para optionals (?T)
const valor2 = funcaoQueRetornaOptional() orelse unreachable;
// Ambos causam panic se a condição ocorrer
// Ambos devem ser usados com extremo cuidado
Dica: Prefira Falhar Graciosamente
Em vez de catch unreachable, considere sempre se existe um caminho de recuperação:
const std = @import("std");
// RUIM: catch unreachable em alocação
fn criarBufferRuim(allocator: std.mem.Allocator) []u8 {
return allocator.alloc(u8, 1024) catch unreachable;
}
// BOM: propagar erro
fn criarBufferBom(allocator: std.mem.Allocator) ![]u8 {
return allocator.alloc(u8, 1024);
}
Erros Relacionados
- Unreachable code — Código inalcançável detectado pelo compilador
- Error not handled — Erro não tratado
- Try in non-error function — Try em função sem retorno de erro
- Attempt to unwrap error — Tentativa de desempacotar error