Zig Comptime Reflection: Introspecção de Tipos

Reflexão em tempo de compilação é uma das features mais poderosas do Zig. Ela permite que você inspecione e manipule tipos durante a compilação — gerando código otimizado e eliminando completamente o overhead de reflexão em runtime.

Neste guia aprofundado, você dominará:

  • Introspecção completa de tipos
  • Manipulação de structs, enums e unions
  • Criação de tipos dinâmicos em comptime
  • Casos de uso práticos

Fundamentos de @typeInfo

O que é TypeInfo?

@typeInfo(T) retorna uma union discriminada (std.builtin.Type) contendo informações sobre o tipo T:

const std = @import("std");

const User = struct {
    name: []const u8,
    age: u32,
};

// Obtém informações do tipo
const info = @typeInfo(User);

// info é uma union do tipo std.builtin.Type
// Para User, info == .Struct

Estrutura do TypeInfo

// std.builtin.Type (simplificado)
pub const Type = union(enum) {
    type: void,
    void: void,
    bool: void,
    int: Int,
    float: Float,
    pointer: Pointer,
    array: Array,
    struct: Struct,
    optional: Optional,
    error_union: ErrorUnion,
    error_set: ErrorSet,
    enum: Enum,
    union: Union,
    fn: Fn,
    bound_fn: BoundFn,
    // ... outros
};

Introspecção de Structs

Informações Básicas

fn analyzeStruct(comptime T: type) void {
    const info = @typeInfo(T);
    
    std.debug.print("=== Análise de {s} ===\n", .{@typeName(T)});
    std.debug.print("Tamanho: {d} bytes\n", .{@sizeOf(T)});
    std.debug.print("Alinhamento: {d}\n", .{@alignOf(T)});
    
    if (info == .Struct) {
        const struct_info = info.Struct;
        
        std.debug.print("Layout: {s}\n", .{@tagName(struct_info.layout)});
        std.debug.print("Campos: {d}\n", .{struct_info.fields.len});
        
        inline for (struct_info.fields) |field| {
            std.debug.print("\n  Campo: {s}\n", .{field.name});
            std.debug.print("    Tipo: {s}\n", .{@typeName(field.type)});
            std.debug.print("    Offset: {d}\n", .{field.offset});
            std.debug.print("    Alinhamento: {d}\n", .{field.alignment});
            
            if (field.default_value) |default| {
                std.debug.print("    Padrão: {any}\n", .{@as(*const field.type, @ptrCast(@alignCast(default))).*});
            }
        }
    }
}

const Point = struct {
    x: f64 = 0.0,
    y: f64 = 0.0,
};

// Uso
comptime analyzeStruct(Point);

// Saída:
// === Análise de Point ===
// Tamanho: 16 bytes
// Alinhamento: 8
// Layout: auto
// Campos: 2
//
//   Campo: x
//     Tipo: f64
//     Offset: 0
//     Alinhamento: 8
//     Padrão: 0.0e+00
//
//   Campo: y
//     Tipo: f64
//     Offset: 8
//     Alinhamento: 8
//     Padrão: 0.0e+00

Iterando sobre Campos

// Função genérica para validar structs
fn validateStruct(value: anytype) !void {
    const T = @TypeOf(value);
    const info = @typeInfo(T);
    
    if (info != .Struct) {
        @compileError("validateStruct requer struct");
    }
    
    inline for (info.Struct.fields) |field| {
        const field_value = @field(value, field.name);
        const FieldType = field.type;
        
        // Validação baseada no tipo
        switch (@typeInfo(FieldType)) {
            .Int => |int_info| {
                if (int_info.signedness == .signed and field_value < 0) {
                    std.debug.print("Aviso: {s}.{s} é negativo\n", .{
                        @typeName(T), field.name
                    });
                }
            },
            .Pointer => |ptr_info| {
                if (ptr_info.size == .Slice and ptr_info.child == u8) {
                    if (field_value.len == 0) {
                        return error.EmptyString;
                    }
                }
            },
            else => {},
        }
    }
}

const Person = struct {
    name: []const u8,
    age: i32,
};

pub fn main() !void {
    const person = Person{
        .name = "Alice",
        .age = -5, // Inválido
    };
    
    try validateStruct(person);
}

Introspecção de Enums

fn analyzeEnum(comptime E: type) void {
    const info = @typeInfo(E);
    
    if (info != .Enum) {
        @compileError("analyzeEnum requer enum");
    }
    
    const enum_info = info.Enum;
    
    std.debug.print("Enum: {s}\n", .{@typeName(E)});
    std.debug.print("Tag type: {s}\n", .{@typeName(enum_info.tag_type)});
    std.debug.print("Variantes: {d}\n", .{enum_info.fields.len});
    
    inline for (enum_info.fields) |field| {
        std.debug.print("  {s} = {d}\n", .{
            field.name,
            field.value
        });
    }
}

const Status = enum(u8) {
    pending = 0,
    active = 1,
    completed = 2,
    failed = 255,
};

comptime analyzeEnum(Status);

// Saída:
// Enum: Status
// Tag type: u8
// Variantes: 4
//   pending = 0
//   active = 1
//   completed = 2
//   failed = 255

Convertendo Enum para Array

// Gera array com todos os valores do enum
fn enumValues(comptime E: type) []const E {
    const info = @typeInfo(E);
    if (info != .Enum) {
        @compileError("enumValues requer enum");
    }
    
    const fields = info.Enum.fields;
    var values: [fields.len]E = undefined;
    
    inline for (fields, 0..) |field, i| {
        values[i] = @field(E, field.name);
    }
    
    return &values;
}

// Gera array com todos os nomes
fn enumNames(comptime E: type) []const []const u8 {
    const info = @typeInfo(E);
    const fields = info.Enum.fields;
    
    var names: [fields.len][]const u8 = undefined;
    inline for (fields, 0..) |field, i| {
        names[i] = field.name;
    }
    
    return &names;
}

pub fn main() !void {
    const values = comptime enumValues(Status);
    const names = comptime enumNames(Status);
    
    for (values, names) |val, name| {
        std.debug.print("{s} = {d}\n", .{name, @intFromEnum(val)});
    }
}

Introspecção de Unions

const Value = union(enum) {
    int: i64,
    float: f64,
    string: []const u8,
    boolean: bool,
};

fn analyzeUnion(comptime U: type) void {
    const info = @typeInfo(U);
    
    if (info != .Union) {
        @compileError("analyzeUnion requer union");
    }
    
    const union_info = info.Union;
    
    std.debug.print("Union: {s}\n", .{@typeName(U)});
    std.debug.print("Tag: {s}\n", .{
        if (union_info.tag_type) |tag|
            @typeName(tag)
        else
            "(untagged)"
    });
    
    inline for (union_info.fields) |field| {
        std.debug.print("  {s}: {s}\n", .{
            field.name,
            @typeName(field.type)
        });
    }
}

comptime analyzeUnion(Value);

Introspecção de Funções

fn analyzeFunction(comptime F: type) void {
    const info = @typeInfo(F);
    
    if (info != .Fn) {
        @compileError("analyzeFunction requer função");
    }
    
    const fn_info = info.Fn;
    
    std.debug.print("Função:\n");
    std.debug.print("  Chamada: {s}\n", .{@tagName(fn_info.calling_convention)});
    std.debug.print("  Parâmetros: {d}\n", .{fn_info.params.len});
    
    inline for (fn_info.params, 0..) |param, i| {
        std.debug.print("    [{d}] {s}{s}\n", .{
            i,
            if (param.name) |n| n else "(unnamed)",
            if (param.type) |t| @typeName(t) else "(anytype)"
        });
    }
    
    if (fn_info.return_type) |ret| {
        std.debug.print("  Retorno: {s}\n", .{@typeName(ret)});
    } else {
        std.debug.print("  Retorno: (naked)\n");
    }
}

fn example(a: i32, b: []const u8) !bool {
    _ = a;
    _ = b;
    return true;
}

comptime analyzeFunction(@TypeOf(example));

// Saída:
// Função:
//   Chamada: Unspecified
//   Parâmetros: 2
//     [0] ai32
//     [1] b[]const u8
//   Retorno: error{...}!bool

Criando Tipos Dinâmicos

Clonando Structs com Modificações

// Cria uma versão opcional de qualquer struct
fn OptionalVersion(comptime T: type) type {
    const info = @typeInfo(T);
    if (info != .Struct) {
        @compileError("OptionalVersion requer struct");
    }
    
    const original_fields = info.Struct.fields;
    var new_fields: [original_fields.len]std.builtin.Type.StructField = undefined;
    
    inline for (original_fields, 0..) |field, i| {
        new_fields[i] = .{
            .name = field.name,
            .type = ?field.type,
            .default_value = &@as(?field.type, null),
            .is_comptime = field.is_comptime,
            .alignment = @alignOf(?field.type),
        };
    }
    
    return @Type(.{
        .Struct = .{
            .layout = info.Struct.layout,
            .fields = &new_fields,
            .decls = &.{},
            .is_tuple = info.Struct.is_tuple,
        },
    });
}

const User = struct {
    name: []const u8,
    email: []const u8,
    age: u32,
};

const PartialUser = OptionalVersion(User);

// PartialUser é equivalente a:
// struct {
//     name: ?[]const u8 = null,
//     email: ?[]const u8 = null,
//     age: ?u32 = null,
// }

Criando Structs de Configuração

fn ConfigStruct(comptime Settings: type) type {
    const info = @typeInfo(Settings);
    
    // Gera campos adicionais para metadata
    const num_fields = info.Struct.fields.len;
    var new_fields: [num_fields + 1]std.builtin.Type.StructField = undefined;
    
    inline for (info.Struct.fields, 0..) |field, i| {
        new_fields[i] = field;
    }
    
    // Adiciona campo de versão
    new_fields[num_fields] = .{
        .name = "__version",
        .type = u32,
        .default_value = &@as(u32, 1),
        .is_comptime = false,
        .alignment = @alignOf(u32),
    };
    
    return @Type(.{
        .Struct = .{
            .layout = .auto,
            .fields = &new_fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

Casos de Uso Práticos

1. Serializador Genérico

fn serialize(
    writer: anytype,
    value: anytype,
) !void {
    const T = @TypeOf(value);
    const info = @typeInfo(T);
    
    switch (info) {
        .Int => try writer.print("{d}", .{value}),
        .Float => try writer.print("{d}", .{value}),
        .Bool => try writer.print("{}", .{value}),
        .Null => try writer.writeAll("null"),
        .Optional => {
            if (value) |v| {
                try serialize(writer, v);
            } else {
                try writer.writeAll("null");
            }
        },
        .Struct => {
            try writer.writeAll("{");
            var first = true;
            inline for (info.Struct.fields) |field| {
                if (!first) try writer.writeAll(", ");
                first = false;
                
                try writer.print("\"{s}\": ", .{field.name});
                try serialize(writer, @field(value, field.name));
            }
            try writer.writeAll("}");
        },
        .Array => {
            try writer.writeAll("[");
            for (value, 0..) |item, i| {
                if (i > 0) try writer.writeAll(", ");
                try serialize(writer, item);
            }
            try writer.writeAll("]");
        },
        .Enum => {
            try writer.print("\"{s}\"", .{@tagName(value)});
        },
        else => @compileError("Tipo não serializável: " ++ @typeName(T)),
    }
}

2. Validador de Schema

fn Validator(comptime T: type) type {
    return struct {
        pub fn validate(value: T) !void {
            const info = @typeInfo(T);
            
            if (info == .Struct) {
                inline for (info.Struct.fields) |field| {
                    const fv = @field(value, field.name);
                    
                    // Validações por convenção de nome
                    if (comptime std.mem.endsWith(u8, field.name, "_id")) {
                        if (fv == 0) {
                            return error.InvalidId;
                        }
                    }
                    
                    if (comptime std.mem.eql(u8, field.name, "email")) {
                        if (!std.mem.contains(u8, fv, "@")) {
                            return error.InvalidEmail;
                        }
                    }
                }
            }
        }
    };
}

3. Diff entre Structs

fn diffStructs(a: anytype, b: @TypeOf(a)) []const u8 {
    const T = @TypeOf(a);
    const info = @typeInfo(T);
    
    if (info != .Struct) {
        @compileError("diffStructs requer struct");
    }
    
    // Comptime: conta mudanças
    var changed_count: usize = 0;
    inline for (info.Struct.fields) |field| {
        if (!eql(@field(a, field.name), @field(b, field.name))) {
            changed_count += 1;
        }
    }
    
    // Gera mensagem
    // (Simplificado - em produção retornaria array de mudanças)
    return "diff detected";
}

fn eql(a: anytype, b: @TypeOf(a)) bool {
    return a == b;
}

Tabela de Propriedades do TypeInfo

TipoPropriedadesExemplo
Int.signedness, .bits@typeInfo(i32).Int.bits == 32
Float.bits@typeInfo(f64).Float.bits == 64
Pointer.size, .child, .is_const@typeInfo(*u8).Pointer.child == u8
Array.len, .child@typeInfo([5]u8).Array.len == 5
Struct.layout, .fields, .declsinfo.Struct.fields[0].name
Enum.tag_type, .fields, .declsinfo.Enum.fields[0].value
Union.layout, .tag_type, .fieldsinfo.Union.tag_type.?
Optional.child@typeInfo(?i32).Optional.child == i32
ErrorUnion.error_set, .payloadinfo.ErrorUnion.payload
Fn.calling_convention, .params, .return_typeinfo.Fn.params.len

Próximos Passos

  1. 🔗 Code Generation em Zig — Crie tipos e funções dinamicamente
  2. SIMD em Zig — Geração de código vetorial com comptime
  3. 🧠 Comptime Deep Dive — Fundamentos da metaprogramação
  4. 📦 Zig Build System — Compile-time code generation

Recursos


Qual é o seu caso de uso favorito para comptime reflection? Compartilhe!

Continue aprendendo Zig

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