Perguntas de Entrevista sobre Comptime em Zig

Perguntas de Entrevista sobre Comptime em Zig

Comptime (compile-time execution) é o recurso mais distintivo de Zig — é o que substitui macros, templates, generics e preprocessador em uma única abstração elegante. Entrevistadores adoram perguntas sobre comptime porque revelam compreensão profunda da linguagem e capacidade de pensar sobre dois momentos de execução distintos (compilação e runtime).

Conceitos Fundamentais

O que é comptime e como difere de macros?

Comptime é a capacidade de executar código Zig em tempo de compilação usando a mesma linguagem e semântica do runtime. Diferente de macros (C/C++) que operam no nível textual ou templates (C++) que são uma sublinguagem, comptime é simplesmente Zig executando em tempo de compilação.

const tamanho = blk: {
    var s: usize = 0;
    for ("hello") |_| s += 1;
    break :blk s;
};
// tamanho é 5, calculado em compilação

Diferenças de macros:

  • Macros operam em texto; comptime opera em valores e tipos
  • Macros não têm type-checking; comptime é totalmente type-safe
  • Macros são difíceis de debugar; comptime usa o mesmo debugger
  • Macros podem gerar código inválido; comptime garante código válido

Como usar comptime em parâmetros de função?

Parâmetros marcados como comptime devem ser conhecidos em tempo de compilação, permitindo que a função tome decisões baseadas neles:

fn criarArray(comptime T: type, comptime tamanho: usize) [tamanho]T {
    var arr: [tamanho]T = undefined;
    for (&arr) |*elem| {
        elem.* = 0;
    }
    return arr;
}

const arr = criarArray(u32, 10); // [10]u32 preenchido com zeros

Explique generic programming em Zig via comptime.

Zig implementa generics usando comptime para parâmetros de tipo:

fn ArrayList(comptime T: type) type {
    return struct {
        items: []T,
        capacity: usize,
        allocator: std.mem.Allocator,

        const Self = @This();

        pub fn init(allocator: std.mem.Allocator) Self {
            return .{
                .items = &[_]T{},
                .capacity = 0,
                .allocator = allocator,
            };
        }

        pub fn append(self: *Self, item: T) !void {
            // ...implementação...
        }
    };
}

Cada instanciação (ArrayList(u32), ArrayList([]const u8)) gera um tipo concreto distinto em tempo de compilação.

Perguntas Intermediárias

O que é @TypeOf e @typeInfo e como usá-los?

@TypeOf retorna o tipo de uma expressão. @typeInfo retorna informação estruturada sobre um tipo:

fn imprimirCampos(comptime T: type) void {
    const info = @typeInfo(T);
    switch (info) {
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                std.debug.print("Campo: {s}, Tipo: {}\n", .{ field.name, field.type });
            }
        },
        else => @compileError("Tipo não suportado"),
    }
}

Isso permite introspecção de tipos em tempo de compilação — serialização, debugging, geração de código.

Como usar inline for e inline while?

Loops inline são desdobrados em tempo de compilação:

fn somaFields(comptime T: type, valor: T) i64 {
    var soma: i64 = 0;
    const fields = @typeInfo(T).@"struct".fields;
    inline for (fields) |field| {
        if (@typeInfo(field.type) == .int) {
            soma += @as(i64, @field(valor, field.name));
        }
    }
    return soma;
}

O loop é completamente desdobrado em compilação — em runtime, é código linear sem branches.

Como comptime se relaciona com o build system?

O build system de Zig (build.zig) é um programa Zig executado em comptime, permitindo lógica arbitrária para configuração de build:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "meu-app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
}

Perguntas Avançadas

Como implementar um serializer genérico usando comptime?

fn serialize(comptime T: type, valor: T, writer: anytype) !void {
    const info = @typeInfo(T);
    switch (info) {
        .int => try writer.writeInt(T, valor, .little),
        .@"struct" => |s| {
            inline for (s.fields) |field| {
                try serialize(field.type, @field(valor, field.name), writer);
            }
        },
        .pointer => |p| {
            if (p.size == .Slice) {
                try writer.writeInt(u32, @intCast(valor.len), .little);
                for (valor) |item| {
                    try serialize(p.child, item, writer);
                }
            }
        },
        else => @compileError("Tipo não suportável: " ++ @typeName(T)),
    }
}

Quais são as limitações de comptime?

  • Não pode fazer IO (ler arquivos, networking) durante compilação (exceto @embedFile)
  • Não pode alocar memória dinâmica ilimitada (há limites de memória do compilador)
  • Loops devem ter número de iterações determinável em compilação
  • Tempo de compilação é limitado
  • Side effects são limitados (não pode modificar estado global persistente)

Quando NÃO usar comptime?

  • Quando o valor realmente só é conhecido em runtime
  • Quando comptime aumenta significativamente o tempo de compilação
  • Quando a metaprogramação torna o código ilegível
  • Para lógica que muda frequentemente (compile-time = recompilação necessária)

Preparação

Comptime é um tópico avançado. Certifique-se de dominar os fundamentos e memória antes de se aprofundar. Pratique com desafios de código e explore exemplos no ecossistema. Veja tutoriais para exercícios práticos de comptime.

Continue aprendendo Zig

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