Unreachable em Zig — O que é e Como Usar

Unreachable em Zig — O que é e Como Usar

Definição

unreachable em Zig é uma expressão que indica ao compilador que determinado ponto do código nunca deveria ser alcançado durante a execução. Se o programa chegar a um unreachable em modo Debug ou ReleaseSafe, será emitido um panic com mensagem de erro. Em ReleaseFast e ReleaseSmall, o compilador assume que aquele ponto é verdadeiramente inalcançável e usa essa informação para otimizações agressivas.

O tipo de unreachable é noreturn — ele nunca produz um valor.

Por que Unreachable Importa

  1. Documentação de intenção: Comunica que certo caminho não é esperado.
  2. Otimização: O compilador pode eliminar branches e simplificar código.
  3. Detecção de bugs: Em Debug, alcançar unreachable causa panic imediato.
  4. Exaustividade: Permite completar switches sem tratar casos impossíveis.

Exemplo Prático

Em Switch Statements

const std = @import("std");

fn diaDaSemana(numero: u3) []const u8 {
    return switch (numero) {
        0 => "Domingo",
        1 => "Segunda",
        2 => "Terça",
        3 => "Quarta",
        4 => "Quinta",
        5 => "Sexta",
        6 => "Sábado",
        7 => unreachable, // u3 vai de 0 a 7, mas só há 7 dias
    };
}

Para Unwrap Seguro

// Quando SABEMOS que o optional não é null
fn primeiroElemento(slice: []const u8) u8 {
    std.debug.assert(slice.len > 0);
    return slice[0];
}

// Ou com error unions quando sabemos que não falha
const valor: u32 = std.fmt.parseInt(u32, "42", 10) catch unreachable;

Em Funções que Nunca Retornam

fn loopInfinito() noreturn {
    while (true) {
        // processa eventos...
    }
    // O compilador sabe que nunca chega aqui
    // Sem unreachable necessário — while(true) já é noreturn
}

fn sairComErro(msg: []const u8) noreturn {
    std.debug.print("ERRO FATAL: {s}\n", .{msg});
    std.process.exit(1);
    // Após exit(), nunca retorna
}

Eliminando Branches Impossíveis

fn dividirSeguro(a: u32, b: u32) u32 {
    if (b == 0) {
        sairComErro("Divisão por zero");
        // sairComErro é noreturn, então o compilador sabe que
        // o código abaixo só executa quando b != 0
    }
    return a / b;
}

Comportamento por Modo de Compilação

ModoAo atingir unreachable
DebugPanic: “reached unreachable”
ReleaseSafePanic: “reached unreachable”
ReleaseFastComportamento indefinido (UB)
ReleaseSmallComportamento indefinido (UB)

Armadilhas Comuns

  • Usar como “TODO”: Não use unreachable como placeholder para código não implementado. Use @panic("TODO") em vez disso.
  • Assumir incorretamente: Se o unreachable for alcançado em Release, o programa terá comportamento indefinido — pode crashar, corromper dados ou continuar silenciosamente.
  • Confundir com undefined: undefined é para valores não inicializados; unreachable é para código que não deveria executar.
  • Otimização prematura: Não coloque unreachable apenas para “ajudar” o compilador. Ele já faz essas otimizações quando possível.

Termos Relacionados

Quando Usar unreachable vs. @panic

A escolha entre unreachable e @panic depende da natureza da condição:

// unreachable: para casos matematicamente impossíveis dado o sistema de tipos
fn descricaoDia(dia: u3) []const u8 {
    return switch (dia) {
        0 => "Domingo",
        1 => "Segunda",
        2 => "Terça",
        3 => "Quarta",
        4 => "Quinta",
        5 => "Sexta",
        6 => "Sábado",
        7 => unreachable, // u3 pode ser 7, mas semanticamente impossível
    };
}

// @panic: para erros de programação que podem ocorrer em prática
fn buscarUsuario(id: u32, usuarios: []const Usuario) Usuario {
    for (usuarios) |u| {
        if (u.id == id) return u;
    }
    // Isso PODE acontecer se o chamador passar um ID inválido
    @panic("buscarUsuario: ID não encontrado — verifique o chamador");
}

// error: para condições esperadas que devem ser tratadas
fn abrirArquivo(path: []const u8) !std.fs.File {
    return std.fs.cwd().openFile(path, .{});
}

A regra geral: use unreachable apenas quando o compilador poderia provar que o código é inalcançável, mas não consegue (por limitações do sistema de tipos). Para qualquer outra condição de erro, prefira @panic com mensagem descritiva ou error unions.

Otimizações Habilitadas por unreachable

Em ReleaseFast, quando o compilador tem garantia de que um branch é unreachable, ele pode:

  • Eliminar o branch completamente do código gerado
  • Assumir que determinados valores estão dentro de um range específico
  • Propagar essa informação para outras partes do código, habilitando otimizações em cadeia

Isso torna unreachable uma ferramenta poderosa para comunicar invariantes ao compilador em código de performance crítica.

Comparação com Outras Linguagens

Em Rust, o macro unreachable!() gera um panic em todos os modos. O equivalente ao unreachable do Zig em ReleaseFast seria std::hint::unreachable_unchecked() no unsafe Rust — que é explicitamente unsafe. Em C, __builtin_unreachable() (GCC/Clang) tem semântica similar ao unreachable do Zig em Release: comportamento indefinido sem verificação em runtime. O Zig diferencia-se por ser seguro por padrão (panic em Debug/ReleaseSafe) e otimizado apenas quando explicitamente solicitado via modo de release.

Tutoriais Relacionados

Continue aprendendo Zig

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