# Error Sets

### Error Sets

An error set is a group of possible error names.

In Zig, errors are not strings. They are not exception objects. They are named values.

For example:

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

This defines an error set named `FileError`.

It contains three possible errors:

```text
error.NotFound
error.PermissionDenied
error.InvalidFormat
```

Each name describes one kind of failure.

### Error Names

An error name starts with `error.` when used as a value.

```zig
return error.NotFound;
```

This means: return the error named `NotFound`.

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

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

const NetworkError = error{
    NotFound,
    Timeout,
};
```

Both sets include `error.NotFound`.

But the sets are different. `FileError` contains only `NotFound`. `NetworkError` contains `NotFound` and `Timeout`.

### Using an Error Set in a Function

You can write a function that returns one of the errors from a specific set:

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

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

    return c - '0';
}
```

The return type is:

```zig
ParseError!u8
```

Read it as:

```text
this function returns either a ParseError or a u8
```

So the function can return:

```text
success: a u8 value
failure: error.EmptyInput or error.InvalidDigit
```

Even though this particular function only returns `InvalidDigit`, the type says it may return anything in `ParseError`.

### Inferred Error Sets

Zig can often infer the error set for you.

Instead of writing:

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

    return c - '0';
}
```

you can write:

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

    return c - '0';
}
```

The `!u8` form means Zig infers the error set from the function body.

For small examples, inferred error sets are convenient.

For public APIs, explicit error sets are often clearer, because the function signature tells readers exactly which errors belong to the contract.

### Any Error

Zig also has the global error set:

```zig
anyerror
```

This means any error name.

For example:

```zig
fn run() anyerror!void {
    return error.SomethingWentWrong;
}
```

This works, but `anyerror` is broad. It tells the caller that almost anything can fail, but not what specifically.

For application code, `anyerror` can be acceptable.

For library code, a smaller explicit error set is usually better. It gives callers a more precise contract.

### Combining Error Sets

Error sets can be merged.

```zig
const ReadError = error{
    NotFound,
    PermissionDenied,
};

const ParseError = error{
    InvalidFormat,
};

const ConfigError = ReadError || ParseError;
```

Now `ConfigError` contains:

```text
error.NotFound
error.PermissionDenied
error.InvalidFormat
```

This is useful when one operation has several phases.

For example, reading a config file can fail while reading the file or while parsing its contents.

```zig
fn loadConfig() ConfigError!void {
    // read file
    // parse file
}
```

The type says exactly what kind of failures are part of this operation.

### Error Sets as Documentation

Error sets are useful because they document failure.

Compare this:

```zig
fn loadConfig() !Config {
```

with this:

```zig
fn loadConfig() ConfigError!Config {
```

The second version gives more information.

A reader can inspect `ConfigError` and see the possible failure cases.

```zig
const ConfigError = error{
    FileNotFound,
    PermissionDenied,
    InvalidSyntax,
    MissingRequiredField,
};
```

This is not just documentation in comments. The compiler also understands it.

### Handling Specific Errors

Because errors are named values, you can handle them with `switch`.

```zig
const result = parseDigit('x') catch |err| switch (err) {
    error.InvalidDigit => {
        std.debug.print("not a digit\n", .{});
        return;
    },
    error.EmptyInput => {
        std.debug.print("empty input\n", .{});
        return;
    },
};
```

This says: if parsing fails, inspect the error and choose what to do.

For small cases, this may feel verbose. In real programs, it becomes useful because each error can get a different response.

A missing file might create a default file.

Permission failure might show a message.

Invalid input might ask the user to try again.

Out of memory might stop the program.

Different failures need different behavior.

### Error Set Coercion

A smaller error set can usually fit into a larger one.

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

const BigError = error{
    NotFound,
    PermissionDenied,
};

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

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

This works because every possible `SmallError` is also inside `BigError`.

The function `big` promises it may return `NotFound` or `PermissionDenied`. Calling `small` can only produce `NotFound`, so the error can be propagated safely.

But the reverse direction would not be safe.

A function that can return `PermissionDenied` cannot be treated as one that only returns `NotFound`.

### Error Sets Keep APIs Honest

Suppose you write this:

```zig
const LoginError = error{
    InvalidPassword,
    UserLocked,
};

fn login() LoginError!void {
    return error.DatabaseDown;
}
```

This is invalid because `DatabaseDown` is not part of `LoginError`.

The compiler prevents the function from returning an error outside its declared contract.

To fix it, you either add the error:

```zig
const LoginError = error{
    InvalidPassword,
    UserLocked,
    DatabaseDown,
};
```

or handle it inside the function instead of returning it.

This is valuable. The error set keeps the function honest.

### Choosing Good Error Names

Good error names should describe the failure clearly.

Weak names:

```zig
error.Bad
error.Failed
error.Unknown
error.Error
```

Better names:

```zig
error.InvalidHeader
error.MissingField
error.PermissionDenied
error.ConnectionClosed
error.UnsupportedVersion
```

The goal is not to write long names. The goal is to make the failure understandable at the call site.

This line should tell the reader something useful:

```zig
return error.UnsupportedVersion;
```

### When to Use Small Error Sets

Use small explicit error sets when you are designing a stable API.

For example:

```zig
const TokenizeError = error{
    InvalidCharacter,
    UnterminatedString,
    InvalidEscape,
};
```

This tells callers exactly what can happen during tokenization.

Small sets are good for:

```text
library APIs
parsers
protocol code
file format readers
database layers
public modules
```

They make your code easier to use because callers can handle every failure intentionally.

### When Inferred Error Sets Are Fine

Inferred error sets are useful when the exact set is not part of the external contract.

For example:

```zig
fn helper() !void {
    try doStepOne();
    try doStepTwo();
    try doStepThree();
}
```

For private helper functions, this is often fine.

The function is internal. The caller is nearby. The exact error set may change as the implementation changes.

For public functions, be more careful. If other code depends on your function, the error set is part of the API.

### The Core Idea

An error set is a precise list of failures.

It lets Zig say:

```text
this function may fail,
and these are the possible reasons
```

That is more useful than a hidden exception, and more precise than a generic failure code.

Error sets are one of the main reasons Zig error handling stays explicit, typed, and readable.

