⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Unions

const std = @import("std");
const expect = std.testing.expect;

// A bare union has no tag: you must know which field is active.
const Payload = union {
    int: i64,
    float: f64,
};

const Tag = enum { int, float, none };

// A tagged union carries its tag, so `switch` can safely discriminate.
const Tagged = union(Tag) {
    int: i64,
    float: f64,
    none: void,
};

// `union(enum)` infers the tag enum for you.
const Inferred = union(enum) {
    text: []const u8,
    number: u32,
};

test "bare union" {
    var p = Payload{ .int = 42 };
    try expect(p.int == 42);
    // Reading `p.float` here would be illegal behaviour: wrong active field.
    p = Payload{ .float = 1.5 };
    try expect(p.float == 1.5);
}

test "switch on a tagged union" {
    var value = Tagged{ .int = 7 };
    switch (value) {
        .int => |*n| n.* += 1,
        .float => |*f| f.* *= 2,
        .none => {},
    }
    try expect(value.int == 8);
}

test "the tag is a real enum value" {
    const value = Tagged{ .float = 2.5 };
    try expect(@as(Tag, value) == .float);
}

test "inferred tag enum" {
    const v = Inferred{ .text = "hi" };
    switch (v) {
        .text => |t| try expect(t.len == 2),
        .number => unreachable,
    }
}

A union holds one of its fields at a time. The question is always: which one?

Bare unions do not know

const Payload = union { int: i64, float: f64 };

Reading a field that is not the active one is illegal behaviour. Bare unions exist for C interop and for cases where the tag is already tracked elsewhere.

Tagged unions do

const Tagged = union(Tag) { int: i64, float: f64, none: void };

Now the value carries its tag, switch can discriminate safely, and the switch must be exhaustive. This is Zig’s sum type, and it is what you almost always want. It is the same construct that makes error unions and optionals work.

Capturing by pointer in a prong lets you mutate in place:

switch (value) {
    .int => |*n| n.* += 1,
    ...
}

union(enum)

Writing union(enum) infers the tag enum from the field names, so you do not have to declare and maintain a parallel enum. Use the explicit form only when you need the enum as a named type of its own, or need to pin its values.

Get the tag with @as(Tag, value), or compare directly against .float in a switch.