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
| Tipo | Propriedades | Exemplo |
|---|---|---|
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, .decls | info.Struct.fields[0].name |
Enum | .tag_type, .fields, .decls | info.Enum.fields[0].value |
Union | .layout, .tag_type, .fields | info.Union.tag_type.? |
Optional | .child | @typeInfo(?i32).Optional.child == i32 |
ErrorUnion | .error_set, .payload | info.ErrorUnion.payload |
Fn | .calling_convention, .params, .return_type | info.Fn.params.len |
Próximos Passos
- 🔗 Code Generation em Zig — Crie tipos e funções dinamicamente
- ⚡ SIMD em Zig — Geração de código vetorial com comptime
- 🧠 Comptime Deep Dive — Fundamentos da metaprogramação
- 📦 Zig Build System — Compile-time code generation
Recursos
Qual é o seu caso de uso favorito para comptime reflection? Compartilhe!