@typeInfo em Zig
O @typeInfo é um dos builtins mais poderosos do Zig para metaprogramação. Ele permite inspecionar qualquer tipo em tempo de compilação, retornando uma union detalhada com todas as informações sobre a estrutura daquele tipo. É fundamental para escrever código genérico e bibliotecas que operam sobre tipos arbitrários.
Sintaxe
@typeInfo(comptime T: type) std.builtin.Type
O que faz
O @typeInfo recebe um tipo como parâmetro e retorna uma instância da union std.builtin.Type, que contém informações detalhadas sobre o tipo fornecido. Essa union possui variantes para cada categoria de tipo em Zig: inteiros, floats, ponteiros, arrays, structs, enums, unions, funções, entre outros.
Com essas informações, é possível escrever funções genéricas que se comportam de forma diferente dependendo das propriedades do tipo recebido, implementar serialização automática, validação de tipos e muito mais.
Parâmetros
- T (
type, comptime): O tipo a ser inspecionado. Deve ser conhecido em tempo de compilação. Pode ser qualquer tipo válido em Zig, incluindo tipos primitivos, structs, enums, unions, ponteiros, arrays e tipos de função.
Valor de retorno
Retorna uma instância de std.builtin.Type, que é uma tagged union com variantes como:
.int— Informações sobre tipos inteiros (bits, sinalização).float— Informações sobre tipos de ponto flutuante.pointer— Informações sobre ponteiros (alinhamento, sentinela, tamanho).array— Informações sobre arrays (comprimento, tipo filho).@"struct"— Informações sobre structs (campos, declarações).@"enum"— Informações sobre enums (campos, tipo de tag).@"union"— Informações sobre unions (campos, tipo de tag).@"fn"— Informações sobre funções (parâmetros, retorno)- E muitas outras variantes.
Exemplos práticos
Exemplo 1: Verificando a categoria de um tipo
const std = @import("std");
fn descreverTipo(comptime T: type) []const u8 {
const info = @typeInfo(T);
return switch (info) {
.int => "tipo inteiro",
.float => "tipo ponto flutuante",
.pointer => "tipo ponteiro",
.array => "tipo array",
.@"struct" => "tipo struct",
.@"enum" => "tipo enum",
.bool => "tipo booleano",
else => "outro tipo",
};
}
test "descrever tipos" {
try std.testing.expectEqualStrings("tipo inteiro", descreverTipo(i32));
try std.testing.expectEqualStrings("tipo ponto flutuante", descreverTipo(f64));
try std.testing.expectEqualStrings("tipo booleano", descreverTipo(bool));
}
Exemplo 2: Inspecionando campos de uma struct
const std = @import("std");
const Pessoa = struct {
nome: []const u8,
idade: u32,
ativo: bool,
};
fn listarCampos(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.@"struct" => |s| {
inline for (s.fields) |campo| {
std.debug.print("Campo: {s}, Tipo: {s}\n", .{
campo.name,
@typeName(campo.type),
});
}
},
else => @compileError("Esperava uma struct"),
}
}
test "listar campos de Pessoa" {
listarCampos(Pessoa);
// Saída:
// Campo: nome, Tipo: []const u8
// Campo: idade, Tipo: u32
// Campo: ativo, Tipo: bool
}
Exemplo 3: Serialização genérica simples
const std = @import("std");
fn toJson(comptime T: type, valor: T, writer: anytype) !void {
const info = @typeInfo(T);
switch (info) {
.int, .comptime_int => try writer.print("{}", .{valor}),
.float, .comptime_float => try writer.print("{d}", .{valor}),
.bool => try writer.print("{}", .{valor}),
.pointer => |ptr| {
if (ptr.size == .slice and ptr.child == u8) {
try writer.print("\"{s}\"", .{valor});
}
},
.@"struct" => |s| {
try writer.writeByte('{');
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.writeByte('}');
},
else => try writer.writeAll("null"),
}
}
Casos de uso comuns
Serialização/Deserialização genérica: Iterar sobre campos de structs para converter automaticamente de/para JSON, MessagePack ou outros formatos.
Validação de tipos genéricos: Em funções genéricas, verificar se o tipo fornecido possui as propriedades necessárias (campos específicos, métodos, etc.).
Geração automática de código: Criar implementações de funções baseadas na estrutura de tipos, como
toString(), comparação, hash, etc.Debug e logging: Imprimir informações detalhadas sobre tipos para depuração em tempo de compilação.
Frameworks e bibliotecas: Construir abstrações que funcionam com qualquer tipo, como ORMs, frameworks web e sistemas de eventos.
Trabalhando com enums via @typeInfo
Inspecionar enums é um dos usos mais comuns de @typeInfo. O campo .@"enum" fornece a lista de variantes, o tipo de tag e se o enum é exaustivo:
const std = @import("std");
fn listarVariantes(comptime E: type) void {
const info = @typeInfo(E);
switch (info) {
.@"enum" => |e| {
std.debug.print("Enum com {} variantes (tag: {s}):\n", .{
e.fields.len,
@typeName(e.tag_type),
});
inline for (e.fields) |campo| {
std.debug.print(" .{s} = {}\n", .{ campo.name, campo.value });
}
},
else => @compileError("Esperava enum"),
}
}
const Direcao = enum(u8) { norte = 0, sul = 1, leste = 2, oeste = 3 };
test "listar enum" {
listarVariantes(Direcao);
}
Verificação de interfaces em comptime
Um padrão poderoso é usar @typeInfo para verificar se um tipo implementa uma “interface” — um conjunto de métodos e campos esperados:
fn verificarInterface(comptime T: type) void {
const info = @typeInfo(T);
if (info != .@"struct") {
@compileError(@typeName(T) ++ " deve ser uma struct");
}
// Verificar se tem método `format`
if (!@hasDecl(T, "format")) {
@compileError(@typeName(T) ++ " deve ter método `format`");
}
// Verificar campo obrigatório
if (!@hasField(T, "id")) {
@compileError(@typeName(T) ++ " deve ter campo `id`");
}
}
Essa técnica é usada extensivamente na biblioteca padrão do Zig para garantir contratos de tipos em tempo de compilação, sem a necessidade de traits ou interfaces explícitas.
Iteração sobre campos de struct em comptime
O padrão inline for com @typeInfo é essencial para metaprogramação genérica:
const std = @import("std");
// Comparação automática de structs campo a campo
fn eql(comptime T: type, a: T, b: T) bool {
const info = @typeInfo(T);
if (info != .@"struct") @compileError("Apenas structs");
inline for (info.@"struct".fields) |campo| {
const va = @field(a, campo.name);
const vb = @field(b, campo.name);
if (va != vb) return false;
}
return true;
}
const Ponto = struct { x: f32, y: f32 };
test "comparação genérica" {
const p1 = Ponto{ .x = 1.0, .y = 2.0 };
const p2 = Ponto{ .x = 1.0, .y = 2.0 };
const p3 = Ponto{ .x = 1.0, .y = 3.0 };
try std.testing.expect(eql(Ponto, p1, p2));
try std.testing.expect(!eql(Ponto, p1, p3));
}
Perguntas Frequentes
P: @typeInfo pode causar erros de compilação?
R: Não por si só — mas o código que usa o resultado pode. Por exemplo, acessar .@"struct" em um tipo que não é struct causaria um erro em runtime de comptime (que é um erro de compilação). Use switch para tratar as variantes corretamente.
P: Qual é a diferença entre @typeInfo e @hasField/@hasDecl?
R: @hasField e @hasDecl são verificações pontuais (sim/não). @typeInfo fornece o conjunto completo de informações sobre o tipo, permitindo inspeção profunda, iteração sobre campos e verificações complexas. Para verificações simples, prefira @hasField/@hasDecl por clareza.
P: Posso usar @typeInfo para inspecionar tipos de erro (anyerror, !T)?
R: Sim. @typeInfo(anyerror) retorna .error_set, e @typeInfo(!T) retorna .error_union com os campos .error_set e .payload. Isso permite metaprogramação sobre conjuntos de erros.
Builtins relacionados
- @typeName — Obtém o nome de um tipo como string
- @TypeOf — Obtém o tipo de uma expressão
- @hasField — Verifica se um tipo possui determinado campo
- @hasDecl — Verifica se um tipo possui determinada declaração
- @field — Acessa campo de struct por nome