Tagged Union em Zig — O que é e Como Usar

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

  1. Modelagem de dados variantes: Representam valores que podem ser de tipos diferentes (ex: token de parser, mensagem de rede).
  2. Segurança: O compilador impede acesso ao campo errado.
  3. Switch exaustivo: Obriga tratamento de todas as variantes.
  4. 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 .circulo quando a variante ativa é .retangulo causa panic em Debug. Use sempre switch.
  • Esquecer variantes no switch: Sem else, o compilador exige exaustividade. Isso é uma vantagem — use-a.
  • Confundir com union simples: union(enum) é tagged; union sem 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

Tutoriais Relacionados

Continue aprendendo Zig

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