Comptime em Zig: Cheatsheet de Generics e @typeInfo

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 deUse
Constante / cálculo na compilaçãocomptime, comptime var, bloco comptime blk:
Generic explícitocomptime T: type retornando type
Generic curto / writeranytype
Introspecção de tipo@typeInfo, @Type, @hasField
Desdobrar campos/enumsinline 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 = &registro;
    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çãoO que realmente acontece
const resultado = dobro(u32, entrada) dentro de uma funçãou32 é 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 structOs 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:

LinguagemMecanismoO que muda no Zig
C/C++#define, macrosNão existem; o próprio código roda em comptime, sem expansão textual nem operador #
C++constexpr / constevalcomptime cobre ambos e vai além: tipos, loops e funções inteiras resolvem na compilação
C++templatesParâmetros comptime T: type com a linguagem normal — sem <T>, sem SFINAE
Rustgenerics + traitscomptime 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

BuiltinDescriçã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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.