# Error Union Types

### Error Union Types

An error union type means:

```text
either an error,
or a normal value
```

You have already seen this shape:

```zig
!i32
```

This means:

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

More explicitly, Zig can also write:

```zig
SomeError!i32
```

This means:

```text
either one error from SomeError,
or an i32
```

So an error union joins two things together:

```text
error set + success type
```

For example:

```zig
const DivideError = error{
    DivisionByZero,
};

fn divide(a: i32, b: i32) DivideError!i32 {
    if (b == 0) {
        return error.DivisionByZero;
    }

    return a / b;
}
```

The return type is:

```zig
DivideError!i32
```

That means the function can produce one of two results:

```text
error.DivisionByZero
```

or:

```text
an i32 value
```

The caller must deal with both possibilities.

### Why This Type Exists

Many operations can fail, but still have a useful result when they succeed.

Opening a file can fail, but if it succeeds, you get a file handle.

Parsing a number can fail, but if it succeeds, you get a number.

Allocating memory can fail, but if it succeeds, you get a slice or pointer.

Reading from a socket can fail, but if it succeeds, you get bytes.

That is exactly what error unions model.

They say:

```text
this operation has a success value,
but it may return an error instead
```

This is more precise than returning a magic value like `-1`, `null`, or `false`.

### A Simple Parse Example

Suppose we want to parse a digit.

```zig
const ParseError = error{
    InvalidDigit,
};

fn parseDigit(c: u8) ParseError!u8 {
    if (c < '0' or c > '9') {
        return error.InvalidDigit;
    }

    return c - '0';
}
```

This function returns `ParseError!u8`.

So the caller cannot treat the result as a plain `u8` immediately.

This is wrong:

```zig
const n: u8 = parseDigit('7');
```

The function does not return only a `u8`. It returns either an error or a `u8`.

You must unwrap it.

### Unwrapping with `try`

The most common way to unwrap an error union is `try`.

```zig
const n: u8 = try parseDigit('7');
```

This means:

```text
if parseDigit succeeds, put the u8 value into n
if parseDigit fails, return the error from the current function
```

Because `try` can return an error from the current function, the current function must also be allowed to fail.

```zig
pub fn main() !void {
    const n = try parseDigit('7');
    std.debug.print("{}\n", .{n});
}
```

Here, `main` returns `!void`, so it can propagate errors.

### Unwrapping with `catch`

Use `catch` when you want to handle the error immediately.

```zig
const n = parseDigit('x') catch 0;
```

This means:

```text
if parseDigit succeeds, use the parsed digit
if parseDigit fails, use 0 instead
```

So `n` becomes `0`.

You can also inspect the error:

```zig
const n = parseDigit('x') catch |err| {
    std.debug.print("parse failed: {}\n", .{err});
    return;
};
```

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

### Error Union Values Are Not Plain Values

This is a key beginner mistake.

If a function returns `!i32`, you do not have an `i32` yet.

You have a value that may contain an `i32`.

So this does not work:

```zig
const result = divide(10, 2);
std.debug.print("{}\n", .{result});
```

The variable `result` has an error union type, not a plain integer type.

You need one of these:

```zig
const result = try divide(10, 2);
```

or:

```zig
const result = divide(10, 2) catch 0;
```

or:

```zig
const result = divide(10, 2) catch |err| {
    std.debug.print("division failed: {}\n", .{err});
    return;
};
```

Only after unwrapping do you have the success value.

### Returning Success and Returning Failure

Inside a function with an error union return type, you can return either kind of value.

```zig
fn divide(a: i32, b: i32) DivideError!i32 {
    if (b == 0) {
        return error.DivisionByZero;
    }

    return a / b;
}
```

This line returns failure:

```zig
return error.DivisionByZero;
```

This line returns success:

```zig
return a / b;
```

The return type allows both.

That is why the type is called an error union. It is a union of two possibilities.

### Explicit Error Set vs Inferred Error Set

This version names the error set:

```zig
fn divide(a: i32, b: i32) DivideError!i32 {
```

This version lets Zig infer it:

```zig
fn divide(a: i32, b: i32) !i32 {
```

Both are error unions.

The difference is the error set.

```zig
!i32
```

means:

```text
some inferred error set, or i32
```

while:

```zig
DivideError!i32
```

means:

```text
DivideError, or i32
```

For teaching examples, `!i32` is shorter.

For serious public APIs, an explicit error set often communicates more clearly.

### Error Union with `void`

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

For those, use:

```zig
!void
```

This means:

```text
either an error,
or success with no value
```

Example:

```zig
fn saveConfig() !void {
    // write file
}
```

If the function succeeds, there is no result to use. Success only means the operation completed.

If it fails, it returns an error.

This shape is common in Zig.

You will see it in functions that write files, create directories, initialize resources, send data, or run setup steps.

### Error Union with Slices and Pointers

Error unions are often used with allocated memory.

```zig
fn makeBuffer(allocator: std.mem.Allocator) ![]u8 {
    return try allocator.alloc(u8, 1024);
}
```

This returns:

```zig
![]u8
```

That means:

```text
either allocation failed,
or you get a mutable byte slice
```

A caller might use it like this:

```zig
const buffer = try makeBuffer(allocator);
defer allocator.free(buffer);
```

The `try` unwraps the slice. After that, `buffer` is a normal `[]u8`.

The `defer` makes sure the buffer is freed later.

### Error Union with Optionals

Do not confuse error unions with optionals.

An optional says:

```text
there may be no value
```

An error union says:

```text
there may be a failure
```

Optional:

```zig
?i32
```

means:

```text
null or i32
```

Error union:

```zig
!i32
```

means:

```text
error or i32
```

They answer different questions.

Use an optional when absence is normal and does not need a reason.

Use an error union when failure has a reason.

Example optional:

```zig
fn findIndex() ?usize {
    return null;
}
```

This means no index was found.

Example error union:

```zig
fn readFile() ![]u8 {
    // may fail because file is missing, permission is denied, etc.
}
```

This means the operation failed for a specific reason.

### Combining Error Union and Optional

Sometimes you need both.

```zig
fn findUser(id: u64) !?User {
    // ...
}
```

Read this carefully:

```zig
!?User
```

It means:

```text
either an error,
or an optional User
```

So there are three possible outcomes:

```text
error: database failed
success: user was found
success: user was not found
```

This is useful when “not found” is not an error.

For example, a database failure is an error. But searching for a user and finding no match may be a valid result.

A caller might write:

```zig
const maybe_user = try findUser(42);

if (maybe_user) |user| {
    std.debug.print("found user: {s}\n", .{user.name});
} else {
    std.debug.print("user not found\n", .{});
}
```

First, `try` handles the error possibility.

Then, `if` handles the optional possibility.

### The Caller Must Choose

When you call a function that returns an error union, you must choose what to do with failure.

You can propagate it:

```zig
const value = try parseDigit(c);
```

You can replace it with a fallback:

```zig
const value = parseDigit(c) catch 0;
```

You can inspect it:

```zig
const value = parseDigit(c) catch |err| switch (err) {
    error.InvalidDigit => 0,
};
```

You can also deliberately ignore it, but Zig makes that decision visible.

The key idea is that failure does not disappear.

### The Core Idea

An error union type is Zig’s way to put success and failure into one return type.

```zig
ErrorSet!T
```

means:

```text
either an error from ErrorSet,
or a value of type T
```

You cannot use the success value until you unwrap the error union.

That is the discipline Zig wants: before using a result, first decide what happens if the operation failed.

