# Union Safety

### Union Safety

A union stores one active field at a time.

That is the most important rule.

A struct groups fields together:

```zig
const Point = struct {
    x: i32,
    y: i32,
};
```

A `Point` has both `x` and `y`.

A union chooses one field:

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

A `Value` is either an `int`, or a `text`, or a `flag`.

It is never all three at once.

#### The Active Field

When you create a tagged union value, you choose the active field:

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

Here, the active field is `text`.

The payload is `"hello"`.

This means the value is not an integer and not a boolean. It is the `text` case.

The safe way to read it is with `switch`:

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

Inside each branch, Zig gives you the payload for that branch.

That is safe because the branch tells Zig which field is active.

#### Why Direct Field Access Can Be Dangerous

A union value only has one active field.

So this thinking is wrong:

```text
value.int exists
value.text exists
value.flag exists
```

Only one exists at a time.

If the value was created like this:

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

then the active field is `text`.

Reading `value.text` matches the active field.

Reading `value.int` does not.

Tagged unions are designed to prevent this kind of mistake. The usual pattern is to use `switch`, because `switch` checks the active tag before giving you the payload.

#### Tagged Unions Are the Safe Default

In Zig, prefer this form:

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

The `enum` tag records which field is active.

That tag gives Zig enough information to check your code.

A tagged union is safe because the value carries its case with it.

You can ask, “Which case is active?” and then handle it.

This is why `union(enum)` is the normal beginner-friendly form.

#### Bare Unions

Zig also has bare unions:

```zig
const Value = union {
    int: i64,
    text: []const u8,
    flag: bool,
};
```

This does not store an automatic tag.

The program must know which field is active by some other means.

That is more dangerous.

With a bare union, the data exists, but the union itself does not remember which field is valid. This can be useful for low-level programming, but it is not the right default.

For ordinary application code, use a tagged union.

#### The Difference

Compare these two definitions:

```zig
const SafeValue = union(enum) {
    int: i64,
    text: []const u8,
    flag: bool,
};

const RawValue = union {
    int: i64,
    text: []const u8,
    flag: bool,
};
```

`SafeValue` stores a tag.

`RawValue` does not.

That one difference changes how you should use them.

| Type | Has active tag? | Best use |
|---|---:|---|
| `union(enum)` | Yes | Normal program modeling |
| `union` | No | Low-level representation work |

A bare union is closer to raw memory control.

A tagged union is closer to safe data modeling.

#### Switching Is the Main Safety Tool

Use `switch` when handling a tagged union.

```zig
fn printValue(value: Value) void {
    switch (value) {
        .int => |n| {
            std.debug.print("int = {}\n", .{n});
        },
        .text => |s| {
            std.debug.print("text = {s}\n", .{s});
        },
        .flag => |b| {
            std.debug.print("flag = {}\n", .{b});
        },
    }
}
```

This has two benefits.

First, the code only reads the payload after checking the active case.

Second, the compiler can check whether all cases are handled.

If you later add a new case:

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

then switches that do not handle `none` can fail to compile.

That is safety. The compiler forces your logic to stay aligned with your data model.

#### Capturing Payloads

A union case with data has a payload.

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

The variable `s` only exists inside that branch.

Its type is known from the union definition:

```zig
text: []const u8
```

So in the branch:

```zig
.text => |s|
```

`s` has type:

```zig
[]const u8
```

For this case:

```zig
.int => |n|
```

`n` has type:

```zig
i64
```

The switch branch narrows the value to the correct payload type.

#### Capturing Mutable Payloads

Sometimes you need to modify the payload inside a union.

Use a pointer capture:

```zig
const Value = union(enum) {
    count: i32,
    text: []const u8,
};

fn increment(value: *Value) void {
    switch (value.*) {
        .count => |*n| {
            n.* += 1;
        },
        .text => {},
    }
}
```

Read this carefully.

The function receives:

```zig
value: *Value
```

That means it receives a pointer to a `Value`.

The switch uses:

```zig
switch (value.*)
```

That means it switches on the actual value behind the pointer.

The branch uses:

```zig
.count => |*n|
```

That captures a pointer to the `count` payload.

Then:

```zig
n.* += 1;
```

modifies the payload.

This is more advanced, but the principle is the same: only touch the payload in the branch where that payload is active.

#### Replacing the Active Field

You can replace a union value with another case:

```zig
var value = Value{ .count = 10 };

value = Value{ .text = "done" };
```

After the assignment, the active field changed.

Before:

```text
active field = count
payload = 10
```

After:

```text
active field = text
payload = "done"
```

You replaced the whole value.

This is safe and common.

#### Do Not Keep Old Payload Pointers After Changing the Tag

Be careful with pointers to union payloads.

Suppose you capture a pointer to a payload, then change the union to another case. That pointer no longer refers to a valid active payload.

Conceptually:

```zig
var value = Value{ .count = 10 };

// pointer to count payload
// then value changes to text
// old pointer should not be used
```

The rule is simple: a payload pointer is only meaningful while that case remains active.

Do not store it and use it after changing the union.

#### Optional Values vs Tagged Unions

An optional is a small special case.

```zig
?i32
```

means:

```text
either no value
or an i32 value
```

A tagged union can express the same idea:

```zig
const MaybeInt = union(enum) {
    none,
    some: i32,
};
```

But Zig already gives you optionals for this common pattern.

Use `?T` when there are only two cases:

```text
missing
present
```

Use a tagged union when there are several meaningful cases:

```zig
const ParseResult = union(enum) {
    ok: i64,
    empty,
    invalid_digit: u8,
    overflow,
};
```

This carries more information than a simple optional.

#### Error Unions vs Tagged Unions

An error union is another special case.

```zig
!i32
```

means:

```text
either an error
or an i32 value
```

Use error unions when the operation either succeeds or fails.

Use tagged unions when the result has several normal shapes.

For example:

```zig
fn readNumber() !i32 {
    // success gives i32
    // failure gives an error
}
```

But for a parser that wants to explain different non-error outcomes:

```zig
const Token = union(enum) {
    number: i64,
    identifier: []const u8,
    symbol: u8,
    end_of_file,
};
```

A token is not “success or failure.” It is one of several valid cases.

#### Invalid States

The main safety benefit of tagged unions is that they prevent invalid combinations.

This struct can represent bad states:

```zig
const Response = struct {
    status: Status,
    body: ?[]const u8,
    redirect_url: ?[]const u8,
};
```

It might contain both a body and a redirect URL.

It might contain neither.

It might say the status is redirect but have no redirect URL.

A tagged union can encode the rules directly:

```zig
const Response = union(enum) {
    ok: []const u8,
    redirect: []const u8,
    not_found,
};
```

Now each case carries exactly what it needs.

`ok` carries a body.

`redirect` carries a URL.

`not_found` carries no payload.

There is no field combination to accidentally misuse.

#### Safety Through Shape

Good type design makes invalid states hard to write.

This is weak:

```zig
const Job = struct {
    kind: JobKind,
    path: ?[]const u8,
    command: ?[]const u8,
};
```

This is stronger:

```zig
const Job = union(enum) {
    read_file: []const u8,
    run_command: []const u8,
    stop,
};
```

The tagged union says exactly what each job is.

A `read_file` job has a path.

A `run_command` job has a command.

A `stop` job has no payload.

The type itself now carries the rule.

#### A Complete Example

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

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

    fn print(self: Event) void {
        switch (self) {
            .connected => {
                std.debug.print("connected\n", .{});
            },
            .disconnected => {
                std.debug.print("disconnected\n", .{});
            },
            .message => |text| {
                std.debug.print("message: {s}\n", .{text});
            },
            .error_code => |code| {
                std.debug.print("error code: {}\n", .{code});
            },
        }
    }
};

pub fn main() void {
    const events = [_]Event{
        .connected,
        Event{ .message = "hello" },
        Event{ .error_code = 500 },
        .disconnected,
    };

    for (events) |event| {
        event.print();
    }
}
```

Output:

```text
connected
message: hello
error code: 500
disconnected
```

The array contains different kinds of events, but every item has the same type: `Event`.

Each event carries only the data that matches its case.

#### The Main Idea

A union is safe when you always respect its active field.

For beginner Zig code, use tagged unions:

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

Then handle them with `switch`:

```zig
switch (value) {
    .int => |n| {},
    .text => |s| {},
    .flag => |b| {},
}
```

A tagged union stores both the case and the payload. That lets Zig check your code and helps you model data without invalid field combinations.

