@panic em Zig
O @panic encerra a execução do programa imediatamente com uma mensagem de erro e, em modo debug, um stack trace. É usado para situações irrecuperáveis onde continuar a execução seria inseguro ou impossível. O tipo de retorno é noreturn — o compilador sabe que o fluxo nunca continua após um panic.
Sintaxe
@panic(message: []const u8) noreturn
Parâmetros
- message (
[]const u8): Mensagem de erro exibida ao encerrar o programa.
Valor de retorno
noreturn — a função nunca retorna. O programa é encerrado.
Exemplos práticos
Exemplo 1: Panic em caso de estado inválido
const std = @import("std");
const Estado = enum { ativo, pausado, finalizado };
fn processar(estado: Estado) void {
switch (estado) {
.ativo => std.debug.print("Processando...\n", .{}),
.pausado => std.debug.print("Em pausa.\n", .{}),
.finalizado => @panic("Tentativa de processar estado finalizado"),
}
}
pub fn main() void {
processar(.ativo);
// processar(.finalizado); // Causaria panic!
}
Exemplo 2: Coerção noreturn em expressões
const std = @import("std");
fn buscarConfig(chave: []const u8) []const u8 {
const configs = [_]struct { k: []const u8, v: []const u8 }{
.{ .k = "host", .v = "localhost" },
.{ .k = "porta", .v = "8080" },
};
for (configs) |c| {
if (std.mem.eql(u8, c.k, chave)) return c.v;
}
// @panic tem tipo noreturn, coercível para []const u8
@panic("Configuração obrigatória não encontrada");
}
pub fn main() void {
const host = buscarConfig("host");
std.debug.print("Host: {s}\n", .{host});
}
Exemplo 3: Validação de pré-condições
const std = @import("std");
const MatrizQuadrada = struct {
dados: []f64,
tamanho: usize,
pub fn init(dados: []f64) MatrizQuadrada {
const n = std.math.sqrt(@as(f64, @floatFromInt(dados.len)));
const tamanho: usize = @intFromFloat(n);
if (tamanho * tamanho != dados.len) {
@panic("Dados não formam uma matriz quadrada");
}
return .{ .dados = dados, .tamanho = tamanho };
}
pub fn get(self: MatrizQuadrada, linha: usize, coluna: usize) f64 {
if (linha >= self.tamanho or coluna >= self.tamanho) {
@panic("Índice fora dos limites da matriz");
}
return self.dados[linha * self.tamanho + coluna];
}
};
Quando usar @panic
- Invariantes violados: Quando uma condição que deveria ser impossível ocorre.
- Configuração ausente: Recursos obrigatórios que não podem ter fallback.
- Bugs: Situações que indicam bug no código, não erro do usuário.
- Protótipos: Placeholder temporário para lógica não implementada.
Quando NÃO usar @panic
- Para erros esperados (use error unions e
try/catch). - Para validação de entrada do usuário (retorne erro).
- Para condições que podem ser tratadas graciosamente.
Comportamento por modo de compilação
O comportamento de @panic varia conforme o modo de otimização escolhido:
- Debug e ReleaseSafe: Imprime a mensagem de panic e um stack trace completo no stderr, depois encerra com código de saída não-zero.
- ReleaseFast e ReleaseSmall: O stack trace pode ser omitido para reduzir tamanho do binário, mas a mensagem ainda é exibida.
- Freestanding (bare metal): O panic chama a função
panicdefinida no root module. Pode ser customizado para piscar um LED de erro, reiniciar o sistema, etc.
Customizando o handler de panic
É possível substituir o comportamento padrão de panic definindo uma função panic no arquivo raiz do projeto:
// main.zig ou root.zig
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = stack_trace;
_ = ret_addr;
std.log.err("PANIC CUSTOMIZADO: {s}", .{message});
// Em embarcados, você pode reiniciar o sistema aqui
std.process.exit(1);
}
Isso é especialmente útil em sistemas embarcados, onde o comportamento padrão de imprimir stack trace não é viável.
Comparação com equivalente em C
Em C, o equivalente mais próximo é assert() ou abort():
#include <assert.h>
#include <stdlib.h>
// C: assert (desativado em release com -DNDEBUG)
assert(x > 0);
// C: abort (sempre ativo, mas sem mensagem)
if (x > 0) abort();
// C: fprintf + abort (mais próximo do @panic)
if (!(x > 0)) {
fprintf(stderr, "x deve ser positivo\n");
abort();
}
Em Zig, @panic é sempre ativo (não pode ser desativado por flag de compilação), fornece mensagem clara e integra com o sistema de stack trace do runtime.
Erros comuns
1. Usar @panic para erros recuperáveis:
// ERRADO: arquivo inexistente não é bug, é condição esperada
fn lerArquivo(caminho: []const u8) []u8 {
const arquivo = std.fs.cwd().openFile(caminho, .{}) catch
@panic("arquivo não encontrado");
// ...
}
// CORRETO: propagar o erro para o chamador decidir
fn lerArquivo(caminho: []const u8) ![]u8 {
const arquivo = try std.fs.cwd().openFile(caminho, .{});
// ...
}
2. Usar @panic com string formatada — @panic aceita apenas strings estáticas. Para mensagens dinâmicas, combine com std.debug.panic:
// ERRADO: @panic não formata strings
// @panic(std.fmt.allocPrint(...)); // não funciona assim
// CORRETO: usar std.debug.panic para mensagens formatadas
std.debug.panic("Valor inválido: {}", .{valor});
Perguntas Frequentes
P: Qual é a diferença entre @panic e unreachable?
R: unreachable é uma asserção de que um ponto do código nunca será alcançado. Em modo Debug, gera panic com mensagem genérica “reached unreachable code”. Em ReleaseFast, torna-se undefined behavior (sem verificação). @panic sempre termina com a mensagem fornecida, em qualquer modo.
P: @panic pode ser chamado em comptime?
R: Não diretamente. Em comptime, use @compileError para sinalizar erros. @panic é exclusivamente runtime.
P: Como capturar um panic nos testes?
R: O framework de testes do Zig trata panics como falhas de teste. Não há como “capturar” um panic de forma controlada — a filosofia do Zig é que panics indicam bugs que devem ser corrigidos, não tratados.
Builtins relacionados
- @compileError — Erro em tempo de compilação (não runtime)
- @src — Informações de localização para debugging
- @compileLog — Debug em tempo de compilação