@tagName em Zig
O @tagName retorna o nome do campo ativo de uma tagged union ou o nome de uma variante de enum como string. É fundamental para serialização, logging e depuração de código que trabalha com unions e enums.
Sintaxe
@tagName(valor: anytype) [:0]const u8
O que faz
O @tagName recebe um valor de enum ou tagged union e retorna o nome do campo/variante ativo como uma string constante terminada em sentinela nulo. Para enums, retorna o nome da variante atual. Para tagged unions, retorna o nome do campo ativo.
Parâmetros
- valor (
anytype): Um valor de tipo enum ou tagged union. O tipo deve ter campos nomeados — unions sem tag não são aceitas.
Valor de retorno
Retorna [:0]const u8 — uma string terminada em zero com o nome do campo ou variante ativo.
Exemplos práticos
Exemplo 1: Nome de variantes de enum
const std = @import("std");
const Estacao = enum { primavera, verao, outono, inverno };
test "tag name de enum" {
const estacao = Estacao.verao;
const nome = @tagName(estacao);
try std.testing.expectEqualStrings("verao", nome);
// Funciona com todas as variantes
try std.testing.expectEqualStrings("primavera", @tagName(Estacao.primavera));
try std.testing.expectEqualStrings("inverno", @tagName(Estacao.inverno));
}
Exemplo 2: Serialização de tagged union
const std = @import("std");
const Forma = union(enum) {
circulo: f64, // raio
retangulo: struct { largura: f64, altura: f64 },
triangulo: struct { base: f64, altura: f64 },
};
fn serializarForma(forma: Forma, writer: anytype) !void {
try writer.print("{{\"tipo\": \"{s}\"", .{@tagName(forma)});
switch (forma) {
.circulo => |raio| {
try writer.print(", \"raio\": {d}", .{raio});
},
.retangulo => |ret| {
try writer.print(", \"largura\": {d}, \"altura\": {d}", .{
ret.largura,
ret.altura,
});
},
.triangulo => |tri| {
try writer.print(", \"base\": {d}, \"altura\": {d}", .{
tri.base,
tri.altura,
});
},
}
try writer.writeAll("}");
}
test "serializar forma" {
var buffer: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buffer);
const writer = stream.writer();
const forma = Forma{ .circulo = 5.0 };
try serializarForma(forma, writer);
const saida = stream.getWritten();
try std.testing.expectEqualStrings("{\"tipo\": \"circulo\", \"raio\": 5}", saida);
}
Exemplo 3: Logging de eventos com tagged union
const std = @import("std");
const Evento = union(enum) {
conexao: struct { ip: []const u8, porta: u16 },
desconexao: struct { motivo: []const u8 },
mensagem: struct { remetente: []const u8, conteudo: []const u8 },
erro: struct { codigo: u32, descricao: []const u8 },
};
fn logEvento(evento: Evento) void {
std.debug.print("[EVENTO:{s}] ", .{@tagName(evento)});
switch (evento) {
.conexao => |c| std.debug.print("{s}:{}\n", .{ c.ip, c.porta }),
.desconexao => |d| std.debug.print("Motivo: {s}\n", .{d.motivo}),
.mensagem => |m| std.debug.print("De {s}: {s}\n", .{ m.remetente, m.conteudo }),
.erro => |e| std.debug.print("Código {}: {s}\n", .{ e.codigo, e.descricao }),
}
}
test "log de eventos" {
logEvento(.{ .conexao = .{ .ip = "192.168.1.1", .porta = 8080 } });
logEvento(.{ .mensagem = .{ .remetente = "Alice", .conteudo = "Olá!" } });
logEvento(.{ .erro = .{ .codigo = 500, .descricao = "Erro interno" } });
}
Casos de uso comuns
- Serialização JSON/XML: Usar o nome da tag como chave de tipo ao serializar unions para formatos textuais.
- Logging estruturado: Incluir o tipo do evento ou estado em mensagens de log.
- Depuração: Imprimir qual variante está ativa durante investigação de bugs.
- Métricas e telemetria: Categorizar eventos por tipo usando o nome da tag.
- Interface de usuário: Exibir o tipo de um valor union para o usuário em formato legível.
Comportamento em comptime
@tagName pode ser usado em tempo de compilação quando o valor é conhecido em comptime. Isso permite construir strings e tabelas de despacho baseadas em variantes de enum sem overhead em runtime:
const std = @import("std");
const Operacao = enum { soma, subtracao, multiplicacao, divisao };
// Mapa de nomes em comptime (sem alocação em runtime)
const NOMES_PT = blk: {
var mapa: [std.meta.fields(Operacao).len][]const u8 = undefined;
mapa[@intFromEnum(Operacao.soma)] = "Soma";
mapa[@intFromEnum(Operacao.subtracao)] = "Subtração";
mapa[@intFromEnum(Operacao.multiplicacao)] = "Multiplicação";
mapa[@intFromEnum(Operacao.divisao)] = "Divisão";
break :blk mapa;
};
fn nomePortugues(op: Operacao) []const u8 {
return NOMES_PT[@intFromEnum(op)];
}
Internacionalização e mapeamento de nomes
@tagName retorna o nome exato do identificador em Zig (inglês ou sem acentos). Para exibição ao usuário em português, use @tagName como chave interna e mantenha um mapeamento separado:
const std = @import("std");
const StatusPedido = enum { pendente, processando, enviado, entregue, cancelado };
fn labelStatus(status: StatusPedido) []const u8 {
return switch (status) {
.pendente => "Aguardando confirmação",
.processando => "Em processamento",
.enviado => "A caminho",
.entregue => "Entregue com sucesso",
.cancelado => "Pedido cancelado",
};
}
fn logStatus(status: StatusPedido) void {
// @tagName para logs internos (inglês, sem espaços)
std.log.info("status_changed tag={s} label={s}", .{
@tagName(status),
labelStatus(status),
});
}
Desempenho
@tagName compila para uma lookup em uma tabela de strings embutida no binário. O custo em runtime é equivalente a um acesso de array indexado pelo valor inteiro do enum/tag — O(1). Não há alocação de memória, pois as strings são constantes estáticas.
Comparação com equivalente em C
Em C, obter o nome de um enum como string requer código manual:
// C: boilerplate manual e propenso a erros de sincronização
typedef enum { PENDENTE, PROCESSANDO, ENVIADO } Status;
const char* nome_status(Status s) {
switch (s) {
case PENDENTE: return "PENDENTE";
case PROCESSANDO: return "PROCESSANDO";
case ENVIADO: return "ENVIADO";
default: return "DESCONHECIDO";
}
}
Em Zig, @tagName elimina completamente esse boilerplate — o compilador mantém os nomes automaticamente:
// Zig: sem boilerplate
const nome = @tagName(status); // sempre sincronizado com o enum
Erros comuns
1. Usar @tagName em unions sem tag:
// ERRO de compilação: @tagName requer tagged union ou enum
const UnionSemTag = union {
a: u32,
b: f32,
};
var u: UnionSemTag = .{ .a = 1 };
// _ = @tagName(u); // erro!
// CORRETO: usar tagged union (union(enum))
const UnionComTag = union(enum) {
a: u32,
b: f32,
};
2. Assumir que @tagName retorna string com encoding específico: A string retornada é ASCII (identificadores Zig são ASCII), então não há preocupação com encoding, mas não espere caracteres especiais ou acentos.
Perguntas Frequentes
P: @tagName funciona com enums que têm valores inteiros explícitos?
R: Sim. O valor inteiro não afeta o nome retornado. @tagName retorna o nome do identificador, independentemente do valor associado:
const Codigo = enum(u8) { ok = 200, nao_encontrado = 404, erro = 500 };
// @tagName(Codigo.nao_encontrado) == "nao_encontrado"
P: Posso usar @tagName com std.meta.stringToEnum para conversão bidirecional?
R: Sim! std.meta.stringToEnum(MeuEnum, nome) faz a operação inversa — converte uma string para o valor de enum correspondente. Juntos, permitem serialização/desserialização simples de enums.
P: O que acontece com variantes de enum geradas por @typeInfo e inline for?
R: @tagName funciona normalmente com qualquer valor de enum válido, independentemente de como foi obtido. Iteração sobre campos via @typeInfo e uso de @tagName é um padrão comum para serialização genérica.
Builtins relacionados
- @errorName — Obtém o nome de um erro como string
- @typeName — Obtém o nome de um tipo
- @intFromEnum — Obtém valor inteiro de um enum
- @typeInfo — Inspeciona tagged unions e enums