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
constPayload=union{
Int: i64,
Float: f64,
Bool:bool,
};
test "simple union"{
var payload =Payload{.Int=1234};
payload.Float=12.34;
}
$ zig test test.zig
1/1 test "simple union"...access of inactive union field
/deps/zig/docgen_tmp/test.zig:8:12:0x204c1bin test "simple union"(test)
payload.Float=12.34;
^
/deps/zig/lib/std/special/test_runner.zig:47:28:0x22bb1ein std.special.main (test)
}else test_fn.func();
^
/deps/zig/lib/std/start.zig:253:37:0x20564din std.start.posixCallMainAndExit (test)
const result = root.main()catch|err|{
^
/deps/zig/lib/std/start.zig:123:5:0x20538fin std.start._start (test)
@call(.{.modifier =.never_inline }, posixCallMainAndExit,.{});
^
Tests failed.Use the following command to reproduce the failure:
/deps/zig/docgen_tmp/test
You can activate another field by assigning the entire union:
test.zig
const std =@import("std");
constassert= std.debug.assert;
constPayload=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
1/1 test "simple union"...OK
All1 tests passed.
In order to use switch with a union, it must be a Tagged union.
To initialize a union when the tag is a comptime-known name, see @unionInit.
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. Tagged unions coerce to their tag type: Type Coercion: unions and enums.
test.zig
const std =@import("std");
constassert= std.debug.assert;
constComplexTypeTag=enum{
Ok,
NotOk,
};
constComplexType=union(ComplexTypeTag){
Ok: u8,
NotOk:void,
};
test "switch on tagged union"{
const c =ComplexType{.Ok=42};
assert(@as(ComplexTypeTag, c)==ComplexTypeTag.Ok);
switch(c){
ComplexTypeTag.Ok=>|value|assert(value ==42),
ComplexTypeTag.NotOk=> unreachable,
}
}
test "@TagType"{
assert(@TagType(ComplexType)==ComplexTypeTag);
}
test "coerce to enum"{
const c1 =ComplexType{.Ok=42};
const c2 =ComplexType.NotOk;
assert(c1 ==.Ok);
assert(c2 ==.NotOk);
}
$ zig test test.zig
1/3 test "switch on tagged union"...OK
2/3 test "@TagType"...OK
3/3 test "coerce to enum"...OK
All3 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");
constassert= std.debug.assert;
constComplexTypeTag=enum{
Ok,
NotOk,
};
constComplexType=union(ComplexTypeTag){
Ok: u8,
NotOk:void,
};
test "modify tagged union in switch"{
var c =ComplexType{.Ok=42};
assert(@as(ComplexTypeTag, c)==ComplexTypeTag.Ok);
switch(c){
ComplexTypeTag.Ok=>|*value| value.*+=1,
ComplexTypeTag.NotOk=> unreachable,
}
assert(c.Ok==43);
}
$ zig test test.zig
1/1 test "modify tagged union in switch"...OK
All1 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");
constassert= std.debug.assert;
constVariant=union(enum){
Int: i32,
Bool:bool,
// void can be omitted when inferring enum tag type.
None,
fn truthy(self:Variant)bool{
returnswitch(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
1/1 test "union method"...OK
All1 tests passed.
@tagName can be used to return a comptime[]const u8
value representing the field name:
test.zig
const std =@import("std");
constassert= std.debug.assert;
constSmall2=union(enum){
A: i32,
B:bool,
C: u8,
};
test "@tagName"{
assert(std.mem.eql(u8,@tagName(Small2.C),"C"));
}
$ zig test test.zig
1/1 test "@tagName"...OK
All1 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.
Anonymous Union Literals
Anonymous Struct Literals syntax can be used to initialize unions without specifying the type:
anon_union.zig
const std =@import("std");
constassert= std.debug.assert;
constNumber=union{
int: i32,
float: f64,
};
test "anonymous union literal syntax"{
var i:Number=.{.int=42};
var f = makeNumber();
assert(i.int==42);
assert(f.float==12.34);
}
fn makeNumber()Number{
return.{.float=12.34};
}
$ zig test anon_union.zig
1/1 test "anonymous union literal syntax"...OK
All1 tests passed.