@compileError em Zig
O @compileError gera um erro de compilação com uma mensagem personalizada. É usado principalmente em código genérico e metaprogramação para rejeitar tipos ou configurações inválidos em tempo de compilação, antes que o programa seja executado. Esse mecanismo garante que problemas sejam detectados o mais cedo possível.
Sintaxe
@compileError(comptime msg: []const u8) noreturn
O que faz
O @compileError interrompe a compilação e exibe a mensagem fornecida como erro. O compilador aponta exatamente a linha onde o @compileError foi invocado, facilitando a identificação do problema. Esse builtin é especialmente poderoso em branches de switch ou if em comptime que representam caminhos de código inválidos.
Parâmetros
- msg (
[]const u8, comptime): A mensagem de erro a ser exibida pelo compilador. Deve ser uma string conhecida em tempo de compilação. Pode ser construída por concatenação de strings literais.
Valor de retorno
O tipo de retorno é noreturn — a compilação é interrompida e nenhum valor é produzido.
Exemplos práticos
Exemplo 1: Validação de tipos em funções genéricas
const std = @import("std");
fn somar(a: anytype, b: anytype) @TypeOf(a, b) {
const T = @TypeOf(a, b);
const info = @typeInfo(T);
switch (info) {
.int, .comptime_int => return a + b,
.float, .comptime_float => return a + b,
else => @compileError(
"somar() requer tipos numéricos, recebeu: " ++ @typeName(T),
),
}
}
test "somar numéricos" {
try std.testing.expect(somar(@as(i32, 10), @as(i32, 20)) == 30);
try std.testing.expect(somar(@as(f64, 1.5), @as(f64, 2.5)) == 4.0);
// A linha abaixo geraria erro de compilação:
// _ = somar("hello", "world");
// Erro: somar() requer tipos numéricos, recebeu: *const [5:0]u8
}
Exemplo 2: Restrições de plataforma
const std = @import("std");
const builtin = @import("builtin");
fn funcaoApenasLinux() void {
if (builtin.os.tag != .linux) {
@compileError("Esta função só é suportada em Linux");
}
// Implementação específica de Linux...
}
fn funcaoApenas64Bits() void {
if (@sizeOf(usize) != 8) {
@compileError("Esta função requer uma arquitetura de 64 bits");
}
// Implementação que depende de ponteiros de 64 bits...
}
Exemplo 3: Validação de campos de struct em comptime
const std = @import("std");
fn validarModelo(comptime T: type) void {
const info = @typeInfo(T);
if (info != .@"struct") {
@compileError("validarModelo requer uma struct, recebeu: " ++ @typeName(T));
}
const fields = info.@"struct".fields;
// Verificar que a struct tem campo "id"
comptime {
var tem_id = false;
for (fields) |f| {
if (std.mem.eql(u8, f.name, "id")) {
tem_id = true;
break;
}
}
if (!tem_id) {
@compileError(@typeName(T) ++ " deve ter um campo 'id'");
}
}
}
const Usuario = struct {
id: u64,
nome: []const u8,
};
// Isso compila normalmente
comptime {
validarModelo(Usuario);
}
// const Invalido = struct { nome: []const u8 };
// validarModelo(Invalido); // Erro: Invalido deve ter um campo 'id'
Casos de uso comuns
- Validação de tipos genéricos: Rejeitar tipos que não satisfazem os requisitos de uma função ou struct genérica.
- Restrições de plataforma: Impedir compilação em plataformas não suportadas.
- Funcionalidade não implementada: Marcar caminhos de código que ainda não foram implementados, diferente de
@panicque ocorre em runtime. - Validação de constantes: Verificar que constantes de configuração têm valores válidos.
- API segura: Guiar desenvolvedores com mensagens de erro claras quando usam uma API incorretamente.
Diferença entre @compileError e @panic
@compileErrorinterrompe a compilação — o programa nunca chega a ser executado.@panicinterrompe a execução — o programa compila mas falha ao encontrar o panic em runtime.
Use @compileError quando o erro pode ser detectado estaticamente. Use @panic para erros que só podem ser detectados em tempo de execução.
Construindo mensagens de erro informativas
A mensagem de @compileError pode ser construída por concatenação de strings literais e @typeName:
fn requerInteiro(comptime T: type) void {
const info = @typeInfo(T);
if (info != .int and info != .comptime_int) {
@compileError(
"Esperado tipo inteiro, recebeu '" ++
@typeName(T) ++
"'. Use tipos como u8, i32, u64."
);
}
}
A mensagem aparece exatamente no ponto onde @compileError é chamado, com o stack de chamadas comptime:
src/lib.zig:5:9: error: Esperado tipo inteiro, recebeu 'f32'. Use tipos como u8, i32, u64.
@compileError em blocos comptime
@compileError é especialmente útil dentro de blocos comptime e inline for para validações complexas:
const Protocolo = struct {
versao: u8,
flags: u16,
payload_len: u32,
};
comptime {
// Verificar que o protocolo tem o tamanho esperado para o wire format
if (@sizeOf(Protocolo) != 7) {
@compileError(
"Protocolo deve ter 7 bytes no wire format, mas tem " ++
std.fmt.comptimePrint("{}", .{@sizeOf(Protocolo)}) ++
" bytes. Verifique o layout."
);
}
}
Equivalente em C
Em C, _Static_assert (C11) é o equivalente mais próximo:
_Static_assert(sizeof(struct Protocolo) == 7, "Tamanho incorreto");
A diferença: _Static_assert tem mensagem estática. @compileError permite mensagens dinâmicas construídas com @typeName, @tagName e concatenação de strings comptime.
Padrão “else unreachable”
Um padrão comum é usar @compileError na branch else de um switch sobre tipos:
fn serializar(comptime T: type, valor: T) []const u8 {
return switch (@typeInfo(T)) {
.int => serializarInt(valor),
.float => serializarFloat(valor),
.bool => if (valor) "true" else "false",
else => @compileError("Tipo não suportado para serialização: " ++ @typeName(T)),
};
}
Isso garante que novos tipos precisem de implementação explícita — o compilador rejeita código que usa a função com tipos não suportados.
Erros comuns
Usar @compileError fora de contexto comptime com condição runtime: Se a condição que leva ao @compileError não for resolvível em comptime, o compilador emitirá um erro diferente — sobre variável runtime em contexto comptime.
String não-comptime na mensagem: A mensagem deve ser uma string literal ou concatenação de strings/valores comptime. Não pode ser uma string dinâmica de runtime.
Perguntas Frequentes
Posso usar std.fmt.comptimePrint para formatar a mensagem?
Sim! std.fmt.comptimePrint formata strings em comptime e pode ser concatenado na mensagem:
@compileError("Tamanho máximo é " ++ std.fmt.comptimePrint("{}", .{MAX}) ++ " bytes");
@compileError afeta o desempenho do binário?
Não. Se @compileError é atingido, a compilação falha e nenhum binário é gerado. Se não é atingido, não existe no código gerado — zero custo em runtime.
Builtins relacionados
- @compileLog — Imprime valores em tempo de compilação para depuração
- @typeName — Útil para construir mensagens de erro descritivas
- @typeInfo — Inspeciona tipos para validação
- @hasField — Verifica existência de campos
- @hasDecl — Verifica existência de declarações