Cheatsheet: Comptime em Zig
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.
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
};
}
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(...) | Imprimir valores durante compilação |
@sizeOf(T) | Tamanho em bytes de um tipo |
@alignOf(T) | Alinhamento de um tipo |
@bitSizeOf(T) | Tamanho em bits de um tipo |
Veja Também
- Tipos de Dados — Tipos fundamentais de Zig
- Funções — Funções genéricas e comptime
- Builtins Zig — Lista completa de builtins
- Type Erasure Pattern — Apagamento de tipo com comptime
- FAQ Performance — Impacto do comptime na performance