A union is a value that may hold one of several types.
A tagged union combines a union with an enum tag. The tag says which field is active.
const Value = union(enum) {
integer: i32,
boolean: bool,
};A Value may contain either an i32 or a bool.
Create values with union literals:
const a = Value{ .integer = 123 };
const b = Value{ .boolean = true };Each value stores both the data and the active tag.
A complete program:
const std = @import("std");
const Value = union(enum) {
integer: i32,
boolean: bool,
};
pub fn main() void {
const a = Value{ .integer = 123 };
const b = Value{ .boolean = true };
std.debug.print("{}\n", .{a});
std.debug.print("{}\n", .{b});
}The output is:
union(enum){ .integer = 123 }
union(enum){ .boolean = true }Tagged unions are usually handled with switch.
const std = @import("std");
const Value = union(enum) {
integer: i32,
boolean: bool,
};
fn print(value: Value) void {
switch (value) {
.integer => |n| {
std.debug.print("integer: {d}\n", .{n});
},
.boolean => |b| {
std.debug.print("boolean: {}\n", .{b});
},
}
}
pub fn main() void {
print(Value{ .integer = 42 });
print(Value{ .boolean = false });
}The output is:
integer: 42
boolean: falseThe syntax:
.integer => |n|means:
- Match the
.integercase. - Extract the payload value.
- Bind it to the name
n.
The extracted value has the correct type.
In:
.integer => |n|n has type i32.
In:
.boolean => |b|b has type bool.
A tagged union is similar to this pair:
const Tag = enum {
integer,
boolean,
};plus:
const Value = struct {
tag: Tag,
...
};but Zig combines them into one checked type.
The compiler prevents reading the wrong field.
const value = Value{ .integer = 10 };
const b = value.boolean;This is invalid because the active field is .integer, not .boolean.
Zig checks this at runtime in safe builds.
A tagged union may be switched without extracting the payload.
switch (value) {
.integer => {
std.debug.print("integer\n", .{});
},
.boolean => {
std.debug.print("boolean\n", .{});
},
}Sometimes only the tag matters.
The tag itself may be obtained with std.meta.activeTag.
const std = @import("std");
const Value = union(enum) {
integer: i32,
boolean: bool,
};
pub fn main() void {
const value = Value{ .integer = 99 };
const tag = std.meta.activeTag(value);
std.debug.print("{}\n", .{tag});
}The output is:
.integerA tagged union may use an explicit enum type.
const Kind = enum {
integer,
boolean,
};
const Value = union(Kind) {
integer: i32,
boolean: bool,
};This is useful when the tag type is needed elsewhere in the program.
Tagged unions are common in parsers.
const Token = union(enum) {
identifier: []const u8,
number: i64,
plus,
minus,
eof,
};Some variants carry data. Others do not.
Values without payloads are written as:
const t = Token.plus;Payload variants use a union literal:
const t = Token{
.number = 123,
};A parser may process tokens like this:
switch (token) {
.identifier => |name| {
std.debug.print("identifier: {s}\n", .{name});
},
.number => |n| {
std.debug.print("number: {d}\n", .{n});
},
.plus => {
std.debug.print("+\n", .{});
},
.minus => {
std.debug.print("-\n", .{});
},
.eof => {
std.debug.print("eof\n", .{});
},
}Tagged unions are also useful for syntax trees.
const Expr = union(enum) {
integer: i64,
add: struct {
left: *Expr,
right: *Expr,
},
subtract: struct {
left: *Expr,
right: *Expr,
},
};Each variant represents one form of expression.
A tagged union stores only one active payload at a time. Its size is large enough for the largest field plus the tag.
A plain union has no safety tag.
const Bits = union {
i: i32,
f: f32,
};Plain unions are mainly for low-level memory work. Most application code should use tagged unions.
Tagged unions are one of Zig’s most important data modeling tools. They replace many uses of inheritance hierarchies and manual tag fields found in C and C++ programs.
Exercises.
7-17. Define a tagged union Shape with variants circle and rectangle.
7-18. Define a tagged union Token with variants number, identifier, and eof.
7-19. Write a function that switches on a tagged union and prints different messages for each variant.
7-20. Define a tagged union where one variant has no payload and another variant stores a struct.