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?