union
A bare union
defines a set of possible types that a value can be as a list of fields. Only one field can be active at a time. The in-memory representation of bare unions is not guaranteed. Bare unions cannot be used to reinterpret memory. For that, use @ptrCast, or use an extern union or a packed union which have guaranteed in-memory layout. Accessing the non-active field is safety-checked Undefined Behavior:
test.zig
const Payload = union {
Int: i64,
Float: f64,
Bool: bool,
};
test "simple union" {
var payload = Payload{ .Int = 1234 };
payload.Float = 12.34;
}
$ zig test test.zig
Test 1/1 simple union...access of inactive union field
/home/andy/dev/zig/docgen_tmp/test.zig:8:12: 0x20410f in ??? (test)
payload.Float = 12.34;
^
/home/andy/dev/zig/build/lib/zig/std/special/test_runner.zig:13:25: 0x225bdb in ??? (test)
if (test_fn.func()) |_| {
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:122:22: 0x225366 in ??? (test)
root.main() catch |err| {
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:43:5: 0x2250d1 in ??? (test)
@noInlineCall(posixCallMainAndExit);
^
Tests failed. Use the following command to reproduce the failure:
/home/andy/dev/zig/docgen_tmp/test
You can activate another field by assigning the entire union:
test.zig
const std = @import("std");
const assert = std.debug.assert;
const Payload = union {
Int: i64,
Float: f64,
Bool: bool,
};
test "simple union" {
var payload = Payload{ .Int = 1234 };
assert(payload.Int == 1234);
payload = Payload{ .Float = 12.34 };
assert(payload.Float == 12.34);
}
$ zig test test.zig
Test 1/1 simple union...OK
All tests passed.
In order to use switch with a union, it must be a Tagged union.
Tagged union
Unions can be declared with an enum tag type. This turns the union into a tagged union, which makes it eligible to use with switch expressions. One can use @TagType to obtain the enum type from the union type.
test.zig
const std = @import("std");
const assert = std.debug.assert;
const ComplexTypeTag = enum {
Ok,
NotOk,
};
const ComplexType = union(ComplexTypeTag) {
Ok: u8,
NotOk: void,
};
test "switch on tagged union" {
const c = ComplexType{ .Ok = 42 };
assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
switch (c) {
ComplexTypeTag.Ok => |value| assert(value == 42),
ComplexTypeTag.NotOk => unreachable,
}
}
test "@TagType" {
assert(@TagType(ComplexType) == ComplexTypeTag);
}
$ zig test test.zig
Test 1/2 switch on tagged union...OK
Test 2/2 @TagType...OK
All tests passed.
In order to modify the payload of a tagged union in a switch expression, place a *
before the variable name to make it a pointer:
test.zig
const std = @import("std");
const assert = std.debug.assert;
const ComplexTypeTag = enum {
Ok,
NotOk,
};
const ComplexType = union(ComplexTypeTag) {
Ok: u8,
NotOk: void,
};
test "modify tagged union in switch" {
var c = ComplexType{ .Ok = 42 };
assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
switch (c) {
ComplexTypeTag.Ok => |*value| value.* += 1,
ComplexTypeTag.NotOk => unreachable,
}
assert(c.Ok == 43);
}
$ zig test test.zig
Test 1/1 modify tagged union in switch...OK
All tests passed.
Unions can be made to infer the enum tag type. Further, unions can have methods just like structs and enums.
test.zig
const std = @import("std");
const assert = std.debug.assert;
const Variant = union(enum) {
Int: i32,
Bool: bool,
// void can be omitted when inferring enum tag type.
None,
fn truthy(self: Variant) bool {
return switch (self) {
Variant.Int => |x_int| x_int != 0,
Variant.Bool => |x_bool| x_bool,
Variant.None => false,
};
}
};
test "union method" {
var v1 = Variant{ .Int = 1 };
var v2 = Variant{ .Bool = false };
assert(v1.truthy());
assert(!v2.truthy());
}
$ zig test test.zig
Test 1/1 union method...OK
All tests passed.
@tagName can be used to return a comptime []const u8
value representing the field name:
test.zig
const std = @import("std");
const assert = std.debug.assert;
const Small2 = union(enum) {
A: i32,
B: bool,
C: u8,
};
test "@tagName" {
assert(std.mem.eql(u8, @tagName(Small2.C), "C"));
}
$ zig test test.zig
Test 1/1 @tagName...OK
All tests passed.
extern union
An extern union
has memory layout guaranteed to be compatible with the target C ABI.
See also:
packed union
A packed union
has well-defined in-memory layout and is eligible to be in a packed struct.