@typeName em Zig
O @typeName é um builtin que retorna o nome de qualquer tipo como uma string legível. É extremamente útil para depuração, logging e geração de mensagens de erro em código genérico. Como opera em tempo de compilação, não há custo em tempo de execução para seu uso.
Sintaxe
@typeName(comptime T: type) *const [N:0]u8
O que faz
O @typeName converte um tipo Zig para sua representação textual como uma string de bytes terminada em sentinela nulo. O nome retornado é o nome totalmente qualificado do tipo, incluindo o módulo de origem quando aplicável. Para tipos anônimos (como closures ou structs literais), o compilador gera um nome descritivo.
Parâmetros
- T (
type, comptime): O tipo cujo nome será retornado. Pode ser qualquer tipo válido em Zig — primitivos, structs, enums, unions, ponteiros, arrays, tipos de função, etc.
Valor de retorno
Retorna um ponteiro para uma string de bytes constante terminada em sentinela zero (*const [N:0]u8), que pode ser usada diretamente como []const u8. O conteúdo é o nome textual do tipo.
Exemplos práticos
Exemplo 1: Nomes de tipos primitivos
const std = @import("std");
test "nomes de tipos primitivos" {
try std.testing.expectEqualStrings("i32", @typeName(i32));
try std.testing.expectEqualStrings("u8", @typeName(u8));
try std.testing.expectEqualStrings("f64", @typeName(f64));
try std.testing.expectEqualStrings("bool", @typeName(bool));
try std.testing.expectEqualStrings("void", @typeName(void));
}
Exemplo 2: Mensagens de erro genéricas
const std = @import("std");
fn processar(comptime T: type, valor: T) !void {
const info = @typeInfo(T);
switch (info) {
.int => {
std.debug.print("Processando inteiro do tipo {s}: {}\n", .{
@typeName(T),
valor,
});
},
.float => {
std.debug.print("Processando float do tipo {s}: {d}\n", .{
@typeName(T),
valor,
});
},
else => {
@compileError("Tipo não suportado: " ++ @typeName(T));
},
}
}
test "processar valores" {
try processar(i32, 42);
try processar(f64, 3.14);
// processar(bool, true); // Erro de compilação: "Tipo não suportado: bool"
}
Exemplo 3: Nomes de tipos compostos
const std = @import("std");
const Ponto = struct {
x: f32,
y: f32,
};
const Cor = enum { vermelho, verde, azul };
test "nomes de tipos compostos" {
// Structs incluem o caminho do módulo
std.debug.print("Struct: {s}\n", .{@typeName(Ponto)});
// Enums também
std.debug.print("Enum: {s}\n", .{@typeName(Cor)});
// Tipos compostos mostram estrutura
std.debug.print("Slice: {s}\n", .{@typeName([]const u8)});
std.debug.print("Ponteiro: {s}\n", .{@typeName(*Ponto)});
std.debug.print("Optional: {s}\n", .{@typeName(?i32)});
std.debug.print("Array: {s}\n", .{@typeName([5]u8)});
// Saída típica:
// Struct: type-name.Ponto
// Enum: type-name.Cor
// Slice: []const u8
// Ponteiro: *type-name.Ponto
// Optional: ?i32
// Array: [5]u8
}
Casos de uso comuns
Depuração de código genérico: Quando uma função genérica não se comporta como esperado,
@typeNameajuda a identificar exatamente qual tipo está sendo processado.Mensagens de erro em comptime: Combinar
@typeNamecom@compileErrorpara gerar mensagens de erro claras quando um tipo inválido é passado a uma função genérica.Logging e telemetria: Incluir o nome do tipo em mensagens de log para rastreabilidade.
Serialização: Usar o nome do tipo como chave em formatos de serialização que precisam preservar informações de tipo.
Geração de documentação: Ferramentas que geram documentação automaticamente podem usar
@typeNamepara descrever tipos.
Nomes de tipos anônimos
Tipos criados inline (structs literais, closures, tipos de retorno de funções genéricas) recebem nomes gerados pelo compilador. Esses nomes são descritivos mas podem parecer estranhos fora de contexto:
const std = @import("std");
test "nomes de tipos anônimos" {
// Struct anônima: algo como "struct { x: i32, y: i32 }"
const PontoAnonimo = struct { x: i32, y: i32 };
std.debug.print("{s}\n", .{@typeName(PontoAnonimo)});
// Função que retorna tipo: nome inclui parâmetros
const T = std.ArrayList(u8);
std.debug.print("{s}\n", .{@typeName(T)});
// "array_list.ArrayListAligned(u8,null)"
}
Não confie no formato exato dos nomes de tipos anônimos entre versões do Zig — eles podem mudar. Use @typeName para diagnóstico e logging, não como chave de dados persistentes.
Geração de mensagens de erro claras em código genérico
A combinação de @typeName com @compileError é o padrão principal para dar feedback útil ao usuário de uma função genérica:
fn assertInteiro(comptime T: type) void {
switch (@typeInfo(T)) {
.int, .comptime_int => {}, // OK
else => @compileError(
"Esperava tipo inteiro, mas recebeu: " ++ @typeName(T)
),
}
}
fn quadrado(valor: anytype) @TypeOf(valor) {
assertInteiro(@TypeOf(valor));
return valor * valor;
}
// quadrado(3.14) → erro de compilação:
// "Esperava tipo inteiro, mas recebeu: comptime_float"
Comparação com @typeInfo
@typeName e @typeInfo são complementares:
| Aspecto | @typeName | @typeInfo |
|---|---|---|
| Retorno | []const u8 (string) | std.builtin.Type (union) |
| Uso principal | Logging, debug, erros | Metaprogramação, introspecção |
| Informação | Nome legível | Estrutura completa |
| Comptime | Sim | Sim |
Use @typeName quando precisar do nome como texto. Use @typeInfo quando precisar inspecionar a estrutura do tipo programaticamente.
Comparação com equivalente em C
Em C, não existe equivalente direto em runtime. O mais próximo é typeid do C++ ou macros customizadas:
// C: não tem equivalente nativo — requer macros ou RTTI do C++
#ifdef __cplusplus
#include <typeinfo>
const char* nome = typeid(int).name(); // "i" (mangled, não legível)
#endif
// Alternativa comum em C: enums manuais + switch
Em Zig, @typeName é parte da linguagem, sempre disponível, sem dependência de RTTI ou bibliotecas externas, e retorna nomes legíveis (não mangled):
std.debug.print("{s}\n", .{@typeName(i32)}); // "i32" — sempre legível
Erros comuns
1. Usar @typeName para comparação de tipos:
// ERRADO: comparar strings de tipos é frágil
if (std.mem.eql(u8, @typeName(T), "i32")) { ... }
// CORRETO: comparar tipos diretamente
if (T == i32) { ... }
// Ou usar @typeInfo para verificações mais complexas
2. Esperar nomes sem caminho de módulo para tipos definidos pelo usuário:
// O nome inclui o módulo: "meu_modulo.MinhaStruct", não apenas "MinhaStruct"
const nome = @typeName(MinhaStruct);
// Use std.mem.lastIndexOf(u8, nome, ".") para extrair só o nome local
Perguntas Frequentes
P: O nome retornado por @typeName é garantido entre versões do Zig?
R: Para tipos primitivos (i32, u8, f64, bool, etc.) e tipos compostos com sintaxe padrão ([]u8, *const u32, ?i64), o formato é estável. Para tipos de structs, enums e unions definidas pelo usuário, o nome inclui o caminho do módulo e pode mudar se o código for reorganizado.
P: Posso usar @typeName para gerar código ou nomes de variáveis dinamicamente?
R: Em comptime, sim — @typeName retorna uma string comptime que pode ser concatenada com ++ para gerar nomes de declarações ou mensagens. Isso é um padrão de metaprogramação válido em Zig.
P: Qual é o tipo exato do retorno de @typeName?
R: O retorno é *const [N:0]u8, onde N é o comprimento do nome. Esse tipo coerce automaticamente para []const u8, então pode ser usado diretamente em std.debug.print com o especificador {s}.
Builtins relacionados
- @typeInfo — Obtém informações detalhadas sobre um tipo
- @TypeOf — Obtém o tipo de uma expressão
- @errorName — Obtém o nome de um erro como string
- @tagName — Obtém o nome do campo ativo de uma tagged union
- @compileError — Gera erro de compilação com mensagem