# Tagged Unions

### Tagged Unions

A tagged union is a type that can store one value from several possible shapes.

Use a tagged union when a value can be different kinds of things, and each kind may carry different data.

For example, imagine a command-line program that accepts several commands:

```zig
const Command = union(enum) {
    quit,
    help,
    open: []const u8,
    write: []const u8,
};
```

This says a `Command` can be one of four cases:

```text
quit
help
open, with a file path
write, with some text
```

The cases `quit` and `help` carry no extra data.

The case `open` carries a string.

The case `write` carries a string.

That is the core idea of a tagged union: one value, several possible cases, and each case may have its own data.

#### Why Not Just Use a Struct?

Suppose we tried to model this with a struct:

```zig
const Command = struct {
    kind: CommandKind,
    path: ?[]const u8,
    text: ?[]const u8,
};
```

This can work, but it allows invalid states.

For example:

```zig
const command = Command{
    .kind = .quit,
    .path = "data.txt",
    .text = "hello",
};
```

What does that mean?

A `quit` command should not have a path or text. But the struct allows it.

A tagged union avoids this. It stores exactly one active case.

```zig
const command = Command.quit;
```

or:

```zig
const command = Command{ .open = "data.txt" };
```

The shape of the value matches the case.

#### Defining a Tagged Union

The syntax is:

```zig
const Name = union(enum) {
    case_name,
    case_name: PayloadType,
};
```

Example:

```zig
const Message = union(enum) {
    ping,
    text: []const u8,
    number: i32,
};
```

A `Message` can be:

```zig
const a = Message.ping;
const b = Message{ .text = "hello" };
const c = Message{ .number = 42 };
```

Only one case is active at a time.

#### Switching on a Tagged Union

Tagged unions work best with `switch`.

```zig
const std = @import("std");

const Message = union(enum) {
    ping,
    text: []const u8,
    number: i32,
};

pub fn main() void {
    const msg = Message{ .text = "hello" };

    switch (msg) {
        .ping => {
            std.debug.print("ping\n", .{});
        },
        .text => |value| {
            std.debug.print("text: {s}\n", .{value});
        },
        .number => |value| {
            std.debug.print("number: {}\n", .{value});
        },
    }
}
```

Output:

```text
text: hello
```

This part is important:

```zig
.text => |value| {
    std.debug.print("text: {s}\n", .{value});
}
```

The `|value|` captures the payload stored in the `text` case.

For `.ping`, there is no payload, so there is nothing to capture.

#### Tags and Payloads

A tagged union has two ideas:

The tag tells you which case is active.

The payload is the data stored by that case.

In this example:

```zig
const Message = union(enum) {
    ping,
    text: []const u8,
    number: i32,
};
```

The possible tags are:

```text
ping
text
number
```

The payloads are:

```text
ping   has no payload
text   has []const u8
number has i32
```

When the value is:

```zig
const msg = Message{ .number = 42 };
```

The active tag is `number`.

The payload is `42`.

#### Accessing the Active Payload

You normally access the payload through `switch`.

```zig
switch (msg) {
    .text => |value| {
        // value is the text payload
    },
    else => {},
}
```

This is safe because Zig only gives you the payload in the branch where that case is active.

Do not think of a union as a struct where all fields exist. Only one field is active.

#### Tagged Unions and Exhaustive Handling

Like enums, tagged unions help Zig check your logic.

If you switch on a tagged union, you should handle all cases:

```zig
switch (msg) {
    .ping => {},
    .text => |value| {
        _ = value;
    },
    .number => |value| {
        _ = value;
    },
}
```

If you later add another case, Zig can force you to update the switch.

That is one of the main reasons tagged unions are useful. They make changing data models safer.

#### Tagged Unions as Results

Tagged unions are useful when a result can have different successful shapes.

Example:

```zig
const LookupResult = union(enum) {
    not_found,
    user: User,
    redirect: []const u8,
};

const User = struct {
    id: u64,
    name: []const u8,
};
```

A lookup may produce:

```text
not_found
a user
a redirect path
```

Each case is clear.

```zig
fn handle(result: LookupResult) void {
    switch (result) {
        .not_found => {
            // show 404
        },
        .user => |user| {
            _ = user;
            // show user page
        },
        .redirect => |path| {
            _ = path;
            // redirect to path
        },
    }
}
```

A plain struct would need optional fields and extra rules. The tagged union puts the rules into the type.

#### Tagged Unions as AST Nodes

Tagged unions are especially useful for parsers and interpreters.

For example, a small expression language might have these expression kinds:

```zig
const Expr = union(enum) {
    integer: i64,
    add: Binary,
    multiply: Binary,

    const Binary = struct {
        left: *Expr,
        right: *Expr,
    };
};
```

An expression can be:

```text
an integer
an addition expression
a multiplication expression
```

Each kind carries the data it needs.

The integer case carries an `i64`.

The addition and multiplication cases carry two child expressions.

This is a common pattern in compilers, interpreters, and data format parsers.

#### Methods on Tagged Unions

A tagged union can contain methods, just like structs and enums.

```zig
const std = @import("std");

const Message = union(enum) {
    ping,
    text: []const u8,
    number: i32,

    fn print(self: Message) void {
        switch (self) {
            .ping => {
                std.debug.print("ping\n", .{});
            },
            .text => |value| {
                std.debug.print("text: {s}\n", .{value});
            },
            .number => |value| {
                std.debug.print("number: {}\n", .{value});
            },
        }
    }
};

pub fn main() void {
    const msg = Message{ .number = 42 };
    msg.print();
}
```

Output:

```text
number: 42
```

The method receives `self: Message`, then switches on it.

This keeps behavior close to the type.

#### Changing a Tagged Union Value

If a tagged union value is mutable, you can replace it with another case.

```zig
var msg = Message.ping;

msg = Message{ .text = "hello" };
msg = Message{ .number = 123 };
```

At each moment, only one case is active.

First, it is `ping`.

Then it is `text`.

Then it is `number`.

You are not changing several fields. You are replacing the whole active value.

#### Anonymous Tag Type

When you write:

```zig
const Message = union(enum) {
    ping,
    text: []const u8,
    number: i32,
};
```

Zig creates an enum tag type for you.

That is why this is called a tagged union. The union has a tag, and the tag records which field is active.

This is the common form. Beginners should start with `union(enum)`.

There are also bare unions and unions with explicit tag enums, but those are more advanced. For ordinary safe modeling, use tagged unions.

#### Common Mistake: Thinking All Fields Exist

This is wrong thinking:

```text
A Message has ping, text, and number fields.
```

A better model is:

```text
A Message is either ping, or text, or number.
```

Only one case is active.

That is why this works:

```zig
const msg = Message{ .text = "hello" };
```

And why this does not make sense:

```zig
// not a real Message shape
.text = "hello"
.number = 42
```

A tagged union is for alternatives, not for grouping fields together.

Use a struct for grouping.

Use a tagged union for choosing one case from several.

#### Common Mistake: Using Optional Fields Instead

You may be tempted to write this:

```zig
const Value = struct {
    int_value: ?i64 = null,
    string_value: ?[]const u8 = null,
    bool_value: ?bool = null,
};
```

This allows many bad states:

```zig
const value = Value{
    .int_value = 10,
    .string_value = "hello",
    .bool_value = true,
};
```

What is this value supposed to be?

An integer? A string? A boolean?

A tagged union is clearer:

```zig
const Value = union(enum) {
    int: i64,
    string: []const u8,
    boolean: bool,
};
```

Now every value has exactly one kind.

```zig
const value = Value{ .string = "hello" };
```

This is precise.

#### Tagged Union vs Enum

An enum gives you a fixed list of choices, but no extra data per choice.

```zig
const Status = enum {
    pending,
    active,
    failed,
};
```

A tagged union gives you a fixed list of choices, and each choice may carry data.

```zig
const Event = union(enum) {
    connected,
    disconnected,
    message: []const u8,
    error_code: u32,
};
```

Use an enum when names are enough.

Use a tagged union when some cases need data.

#### A Practical Example: Command Parser Result

```zig
const std = @import("std");

const Command = union(enum) {
    quit,
    help,
    open: []const u8,
    write: []const u8,

    fn print(self: Command) void {
        switch (self) {
            .quit => {
                std.debug.print("quit\n", .{});
            },
            .help => {
                std.debug.print("help\n", .{});
            },
            .open => |path| {
                std.debug.print("open: {s}\n", .{path});
            },
            .write => |text| {
                std.debug.print("write: {s}\n", .{text});
            },
        }
    }
};

pub fn main() void {
    const commands = [_]Command{
        .help,
        Command{ .open = "notes.txt" },
        Command{ .write = "hello" },
        .quit,
    };

    for (commands) |command| {
        command.print();
    }
}
```

Output:

```text
help
open: notes.txt
write: hello
quit
```

This example shows why tagged unions are useful. Each command has exactly the data it needs.

`help` needs no data.

`open` needs a path.

`write` needs text.

`quit` needs no data.

The type itself describes those rules.

#### The Main Idea

A tagged union represents one choice from several possible cases.

```zig
const Value = union(enum) {
    int: i64,
    string: []const u8,
    boolean: bool,
};
```

This value is one of those cases:

```zig
const value = Value{ .string = "hello" };
```

Use tagged unions when your data has alternatives. They are one of Zig’s best tools for representing states, messages, commands, parser nodes, protocol events, and results with different shapes.

