Tagged Union em Zig — O que é e Como Usar
Definição
Uma tagged union (union discriminada ou union etiquetada) em Zig é um tipo que pode armazenar um dentre vários tipos de valor, junto com uma “tag” (etiqueta) que indica qual variante está ativa no momento. A tag é tipicamente um enum e é mantida automaticamente pelo compilador.
Diferentemente de unions em C (que são “burras” e não sabem qual campo está ativo), tagged unions em Zig são seguras: o compilador garante que você só acesse o campo correto.
Por que Tagged Unions Importam
- Modelagem de dados variantes: Representam valores que podem ser de tipos diferentes (ex: token de parser, mensagem de rede).
- Segurança: O compilador impede acesso ao campo errado.
- Switch exaustivo: Obriga tratamento de todas as variantes.
- Eficiência: Ocupam apenas o tamanho do maior campo + a tag.
Exemplo Prático
Definição e Uso
const std = @import("std");
const Forma = union(enum) {
circulo: f64, // raio
retangulo: struct {
largura: f64,
altura: f64,
},
triangulo: struct {
base: f64,
altura: f64,
},
pub fn area(self: Forma) f64 {
return switch (self) {
.circulo => |raio| std.math.pi * raio * raio,
.retangulo => |r| r.largura * r.altura,
.triangulo => |t| (t.base * t.altura) / 2.0,
};
}
};
pub fn main() void {
const c = Forma{ .circulo = 5.0 };
const r = Forma{ .retangulo = .{ .largura = 10, .altura = 3 } };
std.debug.print("Área do círculo: {d:.2}\n", .{c.area()});
std.debug.print("Área do retângulo: {d:.2}\n", .{r.area()});
}
Switch Exaustivo
fn descrever(forma: Forma) []const u8 {
return switch (forma) {
.circulo => "É um círculo",
.retangulo => "É um retângulo",
.triangulo => "É um triângulo",
// Se você esquecer uma variante, o compilador emite ERRO
};
}
Tagged Union com Enum Explícito
const TipoEvento = enum {
teclado,
mouse,
janela,
};
const Evento = union(TipoEvento) {
teclado: struct { tecla: u32, pressionada: bool },
mouse: struct { x: i32, y: i32, botao: u8 },
janela: struct { largura: u32, altura: u32 },
};
fn processarEvento(evento: Evento) void {
switch (evento) {
.teclado => |k| {
if (k.pressionada) {
std.debug.print("Tecla {} pressionada\n", .{k.tecla});
}
},
.mouse => |m| {
std.debug.print("Mouse em ({}, {})\n", .{ m.x, m.y });
},
.janela => |j| {
std.debug.print("Janela: {}x{}\n", .{ j.largura, j.altura });
},
}
}
Acessando a Tag
const evento = Evento{ .mouse = .{ .x = 100, .y = 200, .botao = 1 } };
// Acessar a tag como enum
const tag = std.meta.activeTag(evento);
if (tag == .mouse) {
std.debug.print("É evento de mouse!\n", .{});
}
Armadilhas Comuns
- Acessar campo inativo: Tentar ler
.circuloquando a variante ativa é.retangulocausa panic em Debug. Use sempreswitch. - Esquecer variantes no switch: Sem
else, o compilador exige exaustividade. Isso é uma vantagem — use-a. - Confundir com union simples:
union(enum)é tagged;unionsem tag é insegura como em C. - Tamanho: O tamanho da tagged union é o do maior campo + tag. Se um campo for muito maior que os outros, considere usar um ponteiro.
Termos Relacionados
- Union — Unions simples (sem tag)
- Enum — Enumerações
- Struct — Tipos estruturados
- Packed Struct — Structs com layout exato