# Error Union Internals

### Error Union Internals

An error union is a value that can contain either:

```text
an error
```

or:

```text
a successful value
```

You have already seen this shape:

```zig
fn readNumber() !i32 {
    return 42;
}
```

The return type is:

```zig
!i32
```

This means:

```text
either an error, or an i32
```

You can also write the error set explicitly:

```zig
fn readNumber() error{InvalidInput}!i32 {
    return 42;
}
```

This means:

```text
either error.InvalidInput, or an i32
```

The short form:

```zig
!i32
```

lets Zig infer the error set.

#### Error Union as a Type

An error union combines two parts:

```zig
ErrorSet!Payload
```

For example:

```zig
error{NotFound}!usize
```

The error set is:

```zig
error{NotFound}
```

The payload is:

```zig
usize
```

So the full type means:

```text
either error.NotFound, or a usize
```

This is similar in spirit to an optional type:

```zig
?usize
```

But they mean different things.

An optional says:

```text
maybe a value, maybe null
```

An error union says:

```text
maybe a value, maybe a named error
```

`null` gives no reason. An error gives a reason.

#### A Simple Example

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

const ParseError = error{
    Empty,
    InvalidDigit,
};

fn parseOneDigit(text: []const u8) ParseError!u8 {
    if (text.len == 0) {
        return error.Empty;
    }

    const c = text[0];

    if (c < '0' or c > '9') {
        return error.InvalidDigit;
    }

    return c - '0';
}

pub fn main() void {
    const result = parseOneDigit("7");

    if (result) |value| {
        std.debug.print("value: {}\n", .{value});
    } else |err| {
        std.debug.print("error: {}\n", .{err});
    }
}
```

The function return type is:

```zig
ParseError!u8
```

That means the function can return:

```zig
error.Empty
error.InvalidDigit
```

or a successful `u8`.

#### Success and Failure Are One Value

This line does not immediately give you a `u8`:

```zig
const result = parseOneDigit("7");
```

The type of `result` is:

```zig
ParseError!u8
```

It is still an error union.

You must unwrap it before using the `u8`.

```zig
if (result) |value| {
    // value is u8 here
} else |err| {
    // err is ParseError here
}
```

Inside the success block, `value` is the payload.

Inside the error block, `err` is the error.

#### `try` Unwraps the Success Value

Most Zig code uses `try` with error unions.

```zig
const value = try parseOneDigit("7");
```

This means:

```text
If parseOneDigit succeeds, put the u8 into value.
If it returns an error, return that error from the current function.
```

So this:

```zig
fn parseTwoDigits(text: []const u8) ParseError!u8 {
    const first = try parseOneDigit(text[0..1]);
    const second = try parseOneDigit(text[1..2]);

    return first * 10 + second;
}
```

is a compact form of explicit error handling.

The important internal idea is that `try` does not make the error disappear. It either unwraps the success value or propagates the error.

#### `catch` Handles the Error Value

Use `catch` when you want to handle an error locally.

```zig
const value = parseOneDigit("x") catch |err| {
    std.debug.print("could not parse digit: {}\n", .{err});
    return;
};
```

If parsing succeeds, `value` is a `u8`.

If parsing fails, the `catch` block receives the error.

You can also provide a default value:

```zig
const value = parseOneDigit("x") catch 0;
```

This means:

```text
Use the parsed digit if parsing succeeds.
Use 0 if parsing fails.
```

Use this only when a default really makes sense.

#### Error Sets Are Types

This is an error set:

```zig
const FileError = error{
    NotFound,
    PermissionDenied,
    TooLarge,
};
```

It is a type.

Values of this type are written like this:

```zig
error.NotFound
error.PermissionDenied
error.TooLarge
```

A function can return one of those errors:

```zig
fn openConfig() FileError!void {
    return error.NotFound;
}
```

The return type says exactly which errors this function can return.

That is part of the function’s contract.

#### Inferred Error Sets

You can let Zig infer the error set:

```zig
fn openConfig() !void {
    return error.NotFound;
}
```

Here, Zig can infer that the function may return `error.NotFound`.

This is convenient, but explicit error sets can be clearer in public APIs.

For internal helper functions, inferred error sets are often fine.

For library functions, explicit error sets often document the API better.

#### Error Set Coercion

A smaller error set can often be used where a larger error set is expected.

```zig
const SmallError = error{
    NotFound,
};

const BigError = error{
    NotFound,
    PermissionDenied,
};

fn small() SmallError!void {
    return error.NotFound;
}

fn big() BigError!void {
    return try small();
}
```

This works because every `SmallError` is also part of `BigError`.

The opposite direction is not safe:

```zig
fn returnsBig() BigError!void {
    return error.PermissionDenied;
}

fn returnsSmall() SmallError!void {
    return try returnsBig(); // error
}
```

`returnsBig` might return `error.PermissionDenied`, which is not in `SmallError`.

The compiler protects the declared error contract.

#### Error Union vs Optional

Use an optional when absence is enough information.

```zig
fn findIndex(items: []const u8, target: u8) ?usize {
    for (items, 0..) |item, i| {
        if (item == target) return i;
    }

    return null;
}
```

If the item is not found, `null` is enough.

Use an error union when the caller needs to know what went wrong.

```zig
const LoadError = error{
    NotFound,
    PermissionDenied,
    InvalidFormat,
};

fn loadConfig(path: []const u8) LoadError!Config {
    // ...
}
```

Here, failure has different meanings. The caller may handle them differently.

#### Error Union vs Error Set Alone

These two types are different:

```zig
error{NotFound}
```

and:

```zig
error{NotFound}!usize
```

The first is only an error value.

The second is either an error or a `usize`.

Example:

```zig
const e: error{NotFound} = error.NotFound;
```

This stores only an error.

```zig
const x: error{NotFound}!usize = 10;
```

This stores a successful `usize`.

```zig
const y: error{NotFound}!usize = error.NotFound;
```

This stores an error.

#### Payload Type Can Be `void`

Many functions can fail but do not return a useful success value.

Their type often looks like this:

```zig
!void
```

or:

```zig
error{Failed}!void
```

This means:

```text
either an error, or successful completion
```

Example:

```zig
fn saveFile() !void {
    // save the file
}
```

Calling it:

```zig
try saveFile();
```

There is no value to store. Success simply means the function completed.

#### Error Names Are Global

In Zig, error names are global by name.

If two error sets both contain `NotFound`, they refer to the same error name.

```zig
const FileError = error{
    NotFound,
};

const UserError = error{
    NotFound,
};
```

Both contain:

```zig
error.NotFound
```

This makes error set coercion possible, but it also means you should choose clear error names.

Sometimes a more specific name is better:

```zig
error.FileNotFound
error.UserNotFound
```

Use names that make sense in the API.

#### Error Unions Are Not Exceptions

An error union is a normal value.

It is visible in the type.

This function says it can fail:

```zig
fn connect() !Connection
```

This function says it cannot fail:

```zig
fn port() u16
```

There is no hidden exception path. You can see possible failure in the function signature.

That is one of Zig’s core design choices.

#### What “Internals” Means for Beginners

At this stage, you do not need to know the exact memory layout of every error union.

The useful mental model is this:

```text
An error union is a tagged result:
one case is an error,
the other case is a success value.
```

So when you see:

```zig
!T
```

read it as:

```text
error or T
```

When you see:

```zig
E!T
```

read it as:

```text
one of the errors in E, or T
```

Then choose how to handle it:

```zig
try value
```

to propagate errors,

```zig
value catch ...
```

to handle errors,

or:

```zig
if (value) |success| {
    ...
} else |err| {
    ...
}
```

to handle both cases explicitly.

#### The Main Idea

An error union is Zig’s explicit way to represent fallible computation.

```zig
error{InvalidInput}!u8
```

means:

```text
This computation can produce InvalidInput, or it can produce a u8.
```

The compiler makes you respect that type. You cannot quietly treat a fallible result as a normal value.

That is the point.

Failure is part of the type, so failure is part of the program’s design.

