Comptime em Zig exige que uma expressão seja avaliada durante a compilação. Use-o para calcular constantes, passar tipos a funções genéricas e inspecionar campos com @typeInfo. Não confunda com const: um valor imutável ainda pode vir de runtime. Este cheatsheet inclui um arquivo de testes para conferir essa diferença e o comportamento de inline for e @inComptime(). Para o passo a passo, veja o tutorial de comptime.
| Precisa de | Use |
|---|---|
| Constante / cálculo na compilação | comptime, comptime var, bloco comptime blk: |
| Generic explícito | comptime T: type retornando type |
| Generic curto / writer | anytype |
| Introspecção de tipo | @typeInfo, @Type, @hasField |
| Desdobrar campos/enums | inline for / inline while |
| Falhar cedo na compilação | @compileError, @compileLog |
O comptime é um dos recursos mais poderosos de Zig. Ele permite executar código em tempo de compilação, eliminando a necessidade de macros, templates complexos ou geração de código externa. Com comptime, o compilador executa funções, avalia expressões e gera código otimizado antes mesmo do programa rodar.
Const vs comptime: exemplos executáveis
Salve o bloco completo abaixo como comptime-cheatsheet.zig e execute:
zig version
zig test comptime-cheatsheet.zig
Compatibilidade: este arquivo de testes foi validado com Zig 0.15.2. Os demais blocos da página são trechos de referência; não devem ser concatenados em um único arquivo. A sintaxe de reflexão usada aqui inclui .int e .@"struct"; exemplos antigos com .Int e .Struct precisam de adaptação. Confira também a referência oficial de comptime do Zig 0.15.2.
const std = @import("std");
fn dobro(comptime T: type, valor: T) T {
return valor * 2;
}
fn calculadoNaCompilacao() bool {
return @inComptime();
}
fn somarCamposInteiros(valor: anytype) i64 {
const T = @TypeOf(valor);
var total: i64 = 0;
inline for (@typeInfo(T).@"struct".fields) |campo| {
switch (@typeInfo(campo.type)) {
.int => total += @as(i64, @intCast(@field(valor, campo.name))),
else => {},
}
}
return total;
}
test "const pode guardar runtime; tipo comptime nao torna valor comptime" {
var entrada: u32 = 21;
// O ponteiro representa acesso a um valor mutável em runtime.
const ptr = &entrada;
ptr.* += 1;
const resultado = dobro(u32, ptr.*);
try std.testing.expectEqual(@as(u32, 44), resultado);
}
test "bloco comptime calcula um valor antes de executar" {
const soma = comptime blk: {
var total: u32 = 0;
for (0..10) |i| total += @intCast(i);
break :blk total;
};
try std.testing.expectEqual(@as(u32, 45), soma);
}
test "inline for especializa campos mas pode ler dados de runtime" {
const Registro = struct { itens: u16, ajuste: i32, ativo: bool };
var registro = Registro{ .itens = 3, .ajuste = -1, .ativo = true };
const ptr = ®istro;
ptr.itens += 1;
try std.testing.expectEqual(@as(i64, 3), somarCamposInteiros(ptr.*));
}
test "inComptime depende do contexto de avaliacao" {
const durante_compilacao = comptime calculadoNaCompilacao();
const durante_execucao = calculadoNaCompilacao();
try std.testing.expect(durante_compilacao);
try std.testing.expect(!durante_execucao);
}
| Situação | O que realmente acontece |
|---|---|
const resultado = dobro(u32, entrada) dentro de uma função | u32 é conhecido na compilação; entrada pode ser runtime. const só impede reatribuir resultado. |
const soma = comptime blk: { ... } | O bloco inteiro precisa ser avaliável na compilação. Um for normal funciona dentro dele. |
inline for sobre campos de uma struct | Os campos e seus tipos são conhecidos na compilação, mas os valores lidos podem mudar em runtime. |
@inComptime() | Informa o contexto em que a chamada está sendo avaliada; não força a execução em comptime. |
No exemplo de reflexão, o total precisa caber em i64: @intCast não torna qualquer inteiro representável. Para aprofundar a seleção de campos, veja reflexão com comptime.
Conceitos Fundamentais
Variáveis comptime
// Variável conhecida em tempo de compilação
comptime var x: i32 = 0;
x += 1; // avaliado pelo compilador, não gera código de runtime
// Constantes são implicitamente comptime quando possível
const tamanho = 10; // comptime_int
const nome = "Zig"; // comptime conhecido
Parâmetros comptime
// Função com parâmetro comptime — resolvido na compilação
fn criarArray(comptime T: type, comptime tamanho: usize) [tamanho]T {
return [_]T{0} ** tamanho;
}
// Uso — o compilador gera código específico para cada chamada
const arr_i32 = criarArray(i32, 5); // [5]i32
const arr_f64 = criarArray(f64, 10); // [10]f64
Blocos comptime
// Bloco executado inteiramente em tempo de compilação
const resultado = comptime blk: {
var soma: i32 = 0;
for (0..10) |i| {
soma += @as(i32, @intCast(i));
}
break :blk soma; // resultado = 45, calculado na compilação
};
Generics com comptime
Zig implementa generics através de parâmetros comptime type:
const std = @import("std");
fn Pilha(comptime T: type) type {
return struct {
const Self = @This();
itens: []T,
topo: usize,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, capacidade: usize) !Self {
return .{
.itens = try allocator.alloc(T, capacidade),
.topo = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.itens);
}
pub fn push(self: *Self, valor: T) !void {
if (self.topo >= self.itens.len) return error.PilhaCheia;
self.itens[self.topo] = valor;
self.topo += 1;
}
pub fn pop(self: *Self) ?T {
if (self.topo == 0) return null;
self.topo -= 1;
return self.itens[self.topo];
}
};
}
// Uso:
// var pilha = try Pilha(i32).init(allocator, 100);
// defer pilha.deinit();
// try pilha.push(42);
Introspecção de Tipos com @typeInfo
const std = @import("std");
fn nomeDoTipo(comptime T: type) []const u8 {
return @typeName(T);
}
fn ehInteiro(comptime T: type) bool {
return switch (@typeInfo(T)) {
.int => true,
else => false,
};
}
fn ehStruct(comptime T: type) bool {
return switch (@typeInfo(T)) {
.@"struct" => true,
else => false,
};
}
// Listar campos de uma struct
fn listarCampos(comptime T: type) []const std.builtin.Type.StructField {
return switch (@typeInfo(T)) {
.@"struct" => |info| info.fields,
else => @compileError("Esperava struct, recebeu " ++ @typeName(T)),
};
}
Funções comptime Comuns
@typeInfo — Introspecção completa
const std = @import("std");
fn imprimeInfo(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.int => |i| {
std.debug.print("Inteiro: {} bits, signedness: {}\n", .{ i.bits, i.signedness });
},
.float => |f| {
std.debug.print("Float: {} bits\n", .{f.bits});
},
.@"struct" => |s| {
std.debug.print("Struct com {} campos\n", .{s.fields.len});
},
.pointer => |p| {
std.debug.print("Ponteiro para {s}\n", .{@typeName(p.child)});
},
else => {
std.debug.print("Outro tipo: {s}\n", .{@typeName(T)});
},
}
}
@Type — Criar tipos programaticamente
const std = @import("std");
// Criar um tipo inteiro com N bits
fn IntN(comptime bits: u16) type {
return @Type(.{ .int = .{
.signedness = .signed,
.bits = bits,
} });
}
const i24 = IntN(24); // cria um tipo inteiro de 24 bits
@hasField e @hasDecl
const Pessoa = struct {
nome: []const u8,
idade: u32,
pub fn saudacao(self: Pessoa) void {
_ = self;
}
};
// Verificar se struct tem um campo
const tem_nome = @hasField(Pessoa, "nome"); // true
const tem_email = @hasField(Pessoa, "email"); // false
// Verificar se tem uma declaração (método, const, etc.)
const tem_saudacao = @hasDecl(Pessoa, "saudacao"); // true
Serialização Genérica com comptime
const std = @import("std");
fn toJson(comptime T: type, valor: T, writer: anytype) !void {
const info = @typeInfo(T);
switch (info) {
.@"struct" => |s| {
try writer.writeAll("{");
inline for (s.fields, 0..) |campo, i| {
if (i > 0) try writer.writeAll(",");
try writer.print("\"{s}\":", .{campo.name});
try toJson(campo.type, @field(valor, campo.name), writer);
}
try writer.writeAll("}");
},
.int => try writer.print("{d}", .{valor}),
.pointer => |p| {
if (p.size == .slice and p.child == u8) {
try writer.print("\"{s}\"", .{valor});
}
},
else => try writer.writeAll("null"),
}
}
inline for e inline while
const std = @import("std");
// inline for — desdobra o loop em tempo de compilação
fn somarCampos(comptime T: type, valor: T) i64 {
var soma: i64 = 0;
const fields = std.meta.fields(T);
inline for (fields) |campo| {
if (@typeInfo(campo.type) == .int) {
soma += @as(i64, @field(valor, campo.name));
}
}
return soma;
}
// inline while — para loops com condição comptime
fn potencia(comptime base: i32, comptime exp: u32) i32 {
comptime var resultado: i32 = 1;
comptime var i: u32 = 0;
inline while (i < exp) : (i += 1) {
resultado *= base;
}
return resultado;
}
// potencia(2, 10) == 1024, calculado na compilação
@compileError e @compileLog
fn apenasNumericos(comptime T: type) void {
switch (@typeInfo(T)) {
.int, .float => {},
else => @compileError("Tipo " ++ @typeName(T) ++ " não é numérico"),
}
}
// Para depuração de comptime
fn debugComptime(comptime x: i32) i32 {
@compileLog("Valor de x em comptime:", x); // imprime durante compilação
return x * 2;
}
Strings em comptime
const std = @import("std");
// Concatenar strings em comptime
fn prefixar(comptime prefixo: []const u8, comptime texto: []const u8) []const u8 {
return prefixo ++ texto;
}
const resultado = prefixar("zig_", "funcao"); // "zig_funcao"
// Formatar em comptime
fn nomeDoMetodo(comptime campo: []const u8) []const u8 {
return "get_" ++ campo;
}
Padrão: Lookup Table em comptime
fn criarTabelaQuadrados(comptime tamanho: usize) [tamanho]u64 {
var tabela: [tamanho]u64 = undefined;
for (0..tamanho) |i| {
tabela[i] = i * i;
}
return tabela;
}
// Tabela calculada na compilação — zero custo em runtime
const quadrados = criarTabelaQuadrados(256);
// quadrados[16] == 256, acesso direto sem cálculo
Padrão: Interface via comptime
fn Serializavel(comptime T: type) type {
if (!@hasDecl(T, "serializar")) {
@compileError(@typeName(T) ++ " precisa ter método 'serializar'");
}
if (!@hasDecl(T, "deserializar")) {
@compileError(@typeName(T) ++ " precisa ter método 'deserializar'");
}
return struct {
// wrapper ou funcionalidade adicional
};
}
Comptime vs constexpr, Templates e Macros
Quem chega de C++, Rust ou C costuma procurar o equivalente direto. Tabela de correspondência:
| Linguagem | Mecanismo | O que muda no Zig |
|---|---|---|
| C/C++ | #define, macros | Não existem; o próprio código roda em comptime, sem expansão textual nem operador # |
| C++ | constexpr / consteval | comptime cobre ambos e vai além: tipos, loops e funções inteiras resolvem na compilação |
| C++ | templates | Parâmetros comptime T: type com a linguagem normal — sem <T>, sem SFINAE |
| Rust | generics + traits | comptime T: type + @hasDecl/@typeInfo para contratos; especialização sempre estática |
| Java/C# | reflexão em runtime | @typeInfo em comptime: introspecção com custo zero em runtime |
A diferença central: em Zig há uma única linguagem para compile-time e runtime. Não existe uma metalinguagem de macros ou templates para aprender — o mesmo if, for e switch que você escreve em runtime podem rodar em comptime.
Erros Comuns do Comptime (e Como Corrigir)
unable to resolve comptime value
O compilador precisa de um valor conhecido na compilação, mas recebeu algo que só existe em runtime:
fn tamanhoDe(comptime T: type) usize {
return @sizeOf(T);
}
// ERRO: `n` vem de stdin — não é conhecido na compilação
// const n = try leituraDoTeclado();
// var arr: [n]u8 = undefined; // unable to resolve comptime value
// CORRETO: valor fixo em comptime
var arr: [1024]u8 = undefined;
// ou derivado de um comptime conhecido:
const arr2: [@sizeOf(u64)]u8 = undefined;
Regra prática: tamanho de array, valor de comptime var e parâmetros comptime exigem dados conhecidos antes de rodar. Se o valor vem do usuário, do arquivo ou da rede, ele é runtime por definição — use slice/alocação dinâmica.
Loop em comptime precisa de contador comptime
// ERRO: `i` é runtime, não pode alimentar uma comptime var
// comptime var soma = 0;
// for (0..n) |i| { soma += i; } // error: cannot store runtime value
// CORRETO: bloco comptime com inline for
const soma = comptime blk: {
var s: u32 = 0;
for (0..10) |i| s += @intCast(i);
break :blk s;
};
Detectar se está em comptime: @inComptime()
fn contexto() bool {
return @inComptime();
}
// Dentro de uma função:
// const na_compilacao = comptime contexto(); // true
// const na_execucao = contexto(); // false
@inComptime() consulta o contexto de avaliação; ele não transforma um argumento runtime em comptime. Inicializadores no escopo do arquivo são avaliados em comptime, enquanto um const local não força esse contexto. O arquivo de testes acima demonstra os dois casos sem interromper a compilação.
Atenção a @compileLog: ele mostra valores durante a compilação e causa erro de compilação quando é alcançado. Use-o temporariamente para depurar e remova-o antes de rodar os testes. Para mensagens em runtime, use std.debug.print. Não trate @compileLog como um logger de produção.
Tabela de Builtins Comptime
| Builtin | Descrição |
|---|---|
@typeInfo(T) | Informações detalhadas sobre um tipo |
@Type(info) | Criar tipo a partir de TypeInfo |
@typeName(T) | Nome do tipo como string |
@hasField(T, name) | Verificar se struct tem campo |
@hasDecl(T, name) | Verificar se tipo tem declaração |
@field(val, name) | Acessar campo por nome em comptime |
@compileError(msg) | Emitir erro de compilação |
@compileLog(...) | Mostrar valores na compilação e provocar erro; remover após depurar |
@inComptime() | Detectar se o código roda em tempo de compilação |
@sizeOf(T) | Tamanho em bytes de um tipo |
@alignOf(T) | Alinhamento de um tipo |
@bitSizeOf(T) | Tamanho em bits de um tipo |
Detalhe do builtin: @typeInfo. Visão geral: builtins Zig.
Perguntas frequentes
O que é comptime em Zig? É código Zig avaliado na compilação: você calcula valores, gera tipos e especializa funções antes do runtime, sem preprocessador.
Como criar generics?
Com comptime T: type (explícito) ou anytype (inferido na chamada). Ambos resolvem em comptime.
Comptime é a mesma coisa que const?
Não. const fixa imutabilidade; comptime exige que a avaliação aconteça na compilação. Muitos const acabam sendo comptime, mas parâmetros, blocos e inline for são o que tornam a metaprogramação possível.
Veja Também
- Tutorial: Comptime em Zig — guia completo passo a passo
- Reflexão com comptime — introspecção e geração de código
- Metaprogramação sem macros — visão aprofundada
- anytype em Zig — generics com tipo inferido
- Tipos de Dados — tipos fundamentais
@typeInfo— builtin de introspecção