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
- Documentação de intenção: Comunica que certo caminho não é esperado.
- Otimização: O compilador pode eliminar branches e simplificar código.
- Detecção de bugs: Em Debug, alcançar
unreachablecausa panic imediato. - 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
| Modo | Ao atingir unreachable |
|---|---|
| Debug | Panic: “reached unreachable” |
| ReleaseSafe | Panic: “reached unreachable” |
| ReleaseFast | Comportamento indefinido (UB) |
| ReleaseSmall | Comportamento indefinido (UB) |
Armadilhas Comuns
- Usar como “TODO”: Não use
unreachablecomo placeholder para código não implementado. Use@panic("TODO")em vez disso. - Assumir incorretamente: Se o
unreachablefor 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
unreachableapenas para “ajudar” o compilador. Ele já faz essas otimizações quando possível.
Termos Relacionados
- Undefined — Valor não inicializado
- noreturn — Tipo de funções que nunca retornam
- Release Modes — Modos de compilação
- Error Union — Tipos que podem conter erros
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.