🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Tagged Unions in Depth

A tagged union is a union that carries a tag naming which field it currently holds. The tag lets code tell the stored field apart without guessing.

Switch on the tag

A switch reads the tag and captures the payload. Each branch handles one field.

const std = @import("std");
const Value = union(enum) {
    int: i32,
    float: f32,
    text: []const u8,
};
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var v = Value{ .int = 42 };
    switch (v) {
        .int => |n| try out.print("int {d}\n", .{n}),
        .float => |x| try out.print("float {d}\n", .{x}),
        .text => |s| try out.print("text {s}\n", .{s}),
    }
    v = Value{ .text = "hi" };
    switch (v) {
        .int => |n| try out.print("int {d}\n", .{n}),
        .float => |x| try out.print("float {d}\n", .{x}),
        .text => |s| try out.print("text {s}\n", .{s}),
    }
}

Tag name

The tag name shows which field is stored. Reassignment updates the tag with the payload.

const std = @import("std");
const Value = union(enum) {
    int: i32,
    float: f32,
    text: []const u8,
};
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    var v = Value{ .int = 42 };
    try out.print("tag={s}\n", .{@tagName(v)});
    v = Value{ .float = 2.5 };
    try out.print("tag={s}\n", .{@tagName(v)});
}

Union size

The union is sized for its largest field plus its tag. A small field does not shrink it.

const std = @import("std");
const Small = union(enum) {
    int: i32,
    float: f32,
    flag: bool,
};
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    try out.print("small size={d} align={d}\n", .{ @sizeOf(Small), @alignOf(Small) });
}

Example

const std = @import("std");
const Value = union(enum) {
    int: i32,
    float: f32,
    text: []const u8,
};
fn describe(v: Value) []const u8 {
    switch (v) {
        .int => return "int",
        .float => return "float",
        .text => return "text",
    }
}
pub fn main() !void {
    const out = std.io.getStdOut().writer();
    const a = Value{ .int = 7 };
    const b = Value{ .float = 2.5 };
    const c = Value{ .text = "hi" };
    try out.print("{s}\n", .{describe(a)});
    try out.print("{s}\n", .{describe(b)});
    try out.print("{s}\n", .{describe(c)});
}

Key points

  • Tagged union: a tagged union is a union declared with an enum tag, written as union(enum).
  • Tag: a tag is the enum value that records which field a union holds.
  • Payload: a payload is the value stored inside a union field.
  • A switch on a tagged union gives the payload for the matching field only.
  • Tag name: a tag name is the text name of the current tag, given by @tagName.
  • Reassignment changes the payload and the tag together, so the two never disagree.
  • A tagged union is at least as large as its largest payload field.

The function switches on the tag. Each branch returns a name for its field. The caller never reads a field without knowing the tag.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A tagged union holds .float = 2.5. What does @tagName give?