@typeInfo em Zig — Referência e Exemplos

@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

  1. Serialização/Deserialização genérica: Iterar sobre campos de structs para converter automaticamente de/para JSON, MessagePack ou outros formatos.

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

  3. Geração automática de código: Criar implementações de funções baseadas na estrutura de tipos, como toString(), comparação, hash, etc.

  4. Debug e logging: Imprimir informações detalhadas sobre tipos para depuração em tempo de compilação.

  5. Frameworks e bibliotecas: Construir abstrações que funcionam com qualquer tipo, como ORMs, frameworks web e sistemas de eventos.

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

Tutoriais relacionados

Continue aprendendo Zig

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