Catch Reached Unreachable — Como Resolver em Zig

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.