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

  1. const Payload = union {
  2. Int: i64,
  3. Float: f64,
  4. Bool: bool,
  5. };
  6. test "simple union" {
  7. var payload = Payload{ .Int = 1234 };
  8. payload.Float = 12.34;
  9. }
  1. $ zig test test.zig
  2. Test 1/1 simple union...access of inactive union field
  3. /home/andy/dev/zig/docgen_tmp/test.zig:8:12: 0x20410f in ??? (test)
  4. payload.Float = 12.34;
  5. ^
  6. /home/andy/dev/zig/build/lib/zig/std/special/test_runner.zig:13:25: 0x225bdb in ??? (test)
  7. if (test_fn.func()) |_| {
  8. ^
  9. /home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:122:22: 0x225366 in ??? (test)
  10. root.main() catch |err| {
  11. ^
  12. /home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:43:5: 0x2250d1 in ??? (test)
  13. @noInlineCall(posixCallMainAndExit);
  14. ^
  15. Tests failed. Use the following command to reproduce the failure:
  16. /home/andy/dev/zig/docgen_tmp/test

You can activate another field by assigning the entire union:

test.zig

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const Payload = union {
  4. Int: i64,
  5. Float: f64,
  6. Bool: bool,
  7. };
  8. test "simple union" {
  9. var payload = Payload{ .Int = 1234 };
  10. assert(payload.Int == 1234);
  11. payload = Payload{ .Float = 12.34 };
  12. assert(payload.Float == 12.34);
  13. }
  1. $ zig test test.zig
  2. Test 1/1 simple union...OK
  3. 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

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const ComplexTypeTag = enum {
  4. Ok,
  5. NotOk,
  6. };
  7. const ComplexType = union(ComplexTypeTag) {
  8. Ok: u8,
  9. NotOk: void,
  10. };
  11. test "switch on tagged union" {
  12. const c = ComplexType{ .Ok = 42 };
  13. assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
  14. switch (c) {
  15. ComplexTypeTag.Ok => |value| assert(value == 42),
  16. ComplexTypeTag.NotOk => unreachable,
  17. }
  18. }
  19. test "@TagType" {
  20. assert(@TagType(ComplexType) == ComplexTypeTag);
  21. }
  1. $ zig test test.zig
  2. Test 1/2 switch on tagged union...OK
  3. Test 2/2 @TagType...OK
  4. 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

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const ComplexTypeTag = enum {
  4. Ok,
  5. NotOk,
  6. };
  7. const ComplexType = union(ComplexTypeTag) {
  8. Ok: u8,
  9. NotOk: void,
  10. };
  11. test "modify tagged union in switch" {
  12. var c = ComplexType{ .Ok = 42 };
  13. assert(ComplexTypeTag(c) == ComplexTypeTag.Ok);
  14. switch (c) {
  15. ComplexTypeTag.Ok => |*value| value.* += 1,
  16. ComplexTypeTag.NotOk => unreachable,
  17. }
  18. assert(c.Ok == 43);
  19. }
  1. $ zig test test.zig
  2. Test 1/1 modify tagged union in switch...OK
  3. 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

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const Variant = union(enum) {
  4. Int: i32,
  5. Bool: bool,
  6. // void can be omitted when inferring enum tag type.
  7. None,
  8. fn truthy(self: Variant) bool {
  9. return switch (self) {
  10. Variant.Int => |x_int| x_int != 0,
  11. Variant.Bool => |x_bool| x_bool,
  12. Variant.None => false,
  13. };
  14. }
  15. };
  16. test "union method" {
  17. var v1 = Variant{ .Int = 1 };
  18. var v2 = Variant{ .Bool = false };
  19. assert(v1.truthy());
  20. assert(!v2.truthy());
  21. }
  1. $ zig test test.zig
  2. Test 1/1 union method...OK
  3. All tests passed.

@tagName can be used to return a comptime []const u8 value representing the field name:

test.zig

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. const Small2 = union(enum) {
  4. A: i32,
  5. B: bool,
  6. C: u8,
  7. };
  8. test "@tagName" {
  9. assert(std.mem.eql(u8, @tagName(Small2.C), "C"));
  10. }
  1. $ zig test test.zig
  2. Test 1/1 @tagName...OK
  3. 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.