Cheatsheet: Comptime em Zig

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

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(...)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

Continue aprendendo Zig

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