# Error Sets

### Error Sets

Programs fail for many reasons. A file may not exist. Memory allocation may fail. Input may be malformed. A network connection may close unexpectedly.

Zig treats errors as values.

An error in Zig is not an exception. Control does not jump through hidden runtime machinery. A function that may fail says so directly in its type.

Here is a small example:

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

fn openFile() !void {
    const file = try std.fs.cwd().openFile("data.txt", .{});
    defer file.close();

    std.debug.print("file opened\n", .{});
}

pub fn main() !void {
    try openFile();
}
```

The return type of `openFile` is:

```zig
!void
```

This means:

> either return `void`, or return an error.

The `!` forms an error union type. The value is either a normal result or an error.

The call:

```zig
try std.fs.cwd().openFile("data.txt", .{});
```

means:

1. call `openFile`
2. if it succeeds, continue
3. if it fails, return the error immediately

The compiler checks that errors are handled.

Suppose the file does not exist. The program prints something like:

```text
error: FileNotFound
```

`FileNotFound` is an error value.

Error values belong to error sets.

An error set is written like this:

```zig
error{
    FileNotFound,
    AccessDenied,
    OutOfMemory,
}
```

This defines a set of named errors.

A function may return one of them:

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

const ReadError = error{
    EndOfStream,
    InvalidData,
};

fn readNumber(ok: bool) ReadError!u32 {
    if (!ok) {
        return ReadError.InvalidData;
    }

    return 42;
}

pub fn main() !void {
    const n = try readNumber(true);
    std.debug.print("{d}\n", .{n});
}
```

The return type:

```zig
ReadError!u32
```

means:

> either return a `u32`, or one error from `ReadError`.

Errors are namespaced:

```zig
ReadError.InvalidData
```

This avoids collisions between unrelated error names.

A function may return an error directly:

```zig
return ReadError.EndOfStream;
```

or return a value:

```zig
return 42;
```

The caller must deal with both possibilities.

Error sets can be inferred. In practice, many Zig programs use inferred errors for small internal functions and explicit error sets for library boundaries.

This function:

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

    return a / b;
}
```

creates an inferred error set containing:

```zig
error.DivisionByZero
```

The caller may handle the error with `catch`:

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

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

    return a / b;
}

pub fn main() void {
    const n = divide(10, 0) catch {
        std.debug.print("cannot divide by zero\n", .{});
        return;
    };

    std.debug.print("{d}\n", .{n});
}
```

Errors are ordinary values. They are typed, checked by the compiler, and visible in function signatures.

This is one of the central ideas in Zig. A function says exactly how it may fail, and the caller decides what to do about it.

Exercise 8-1. Write a function that returns `error.NegativeNumber` if its argument is less than zero.

Exercise 8-2. Write a function that opens a file and returns `error.EmptyName` if the filename is empty.

Exercise 8-3. Modify `divide` so that it also returns `error.Overflow`.

Exercise 8-4. Write a program that reads a file name from the command line and prints a message if the file cannot be opened.

