Tagged Unions

A tagged union combines a union with a tag that keeps track of which field is active.

const std = @import("std");
const print = std.debug.print;

const ValueTag = enum {
    int,
    float,
    boolean,
};

const Value = union(ValueTag) {
    // the fields here have to match those in the enum
    int: i64,
    float: f64,
    boolean: bool,
};

pub fn main() void {
    var value = Value{ .int = 4 };
    print("int value: {}\n", .{value.int});
    value = .{ .boolean = true };
    print("boolean value: {}\n", .{value.boolean});

    // A tagged union can be cast to its tag.
    const tag: ValueTag = value;
    print("tag is {}\n", .{tag});

    // Access the fields with a switch:
    switch (value) {
        .int => |i| print("int: {d}\n", .{i}),
        .float => |f| print("float: {d:.2}\n", .{f}),
        .boolean => |b| print("boolean: {}\n", .{b}),
    }

    // Modify them using pointers:
    switch (value) {
        .int => |*i| i.* += 1,
        .float => |*f| f.* += 1.0,
        .boolean => |*b| b.* = !b.*,
    }
    print("boolean value: {}\n", .{value.boolean});
}
$ zig run tagged.zig
int value: 4
boolean value: true
tag is .boolean
boolean: true
boolean value: false

Inferred tag type

The compiler can infer the tag type for us:

const std = @import("std");
const print = std.debug.print;

const Value = union(enum) {
    int: i64,
    float: f64,
    boolean: bool,

    fn to_float(self: Value) f64 {
        return switch (self) {
            .int => |i| @floatFromInt(i),
            .float => |f| f,
            .boolean => |b| if (b) 1.0 else 0.0,
        };
    }
};

pub fn main() void {
    const value = Value{ .int = 4 };
    print("as float: {d:.2}\n", .{value.to_float()});
}
$ zig run tagged-2.zig 
as float: 4.00

Next example: Allocators.