# Errors, Optionals, try, catch and errdefer — Zig

Source: https://www.geekswithgeeks.com/en/zig/t-errors

> Handle failures with error unions and missing values with optionals.

## Errors are values

Zig has no exceptions. A function that can fail returns an **error union** `E!T`: either a value of type `T` or an error from the **error set** `E`. Error sets are declared like `const ParseError = error{ Empty, InvalidQuantity };`, and `!T` alone asks the compiler to **infer** the error set. Errors are lightweight values (like enum tags), and they **cannot be ignored**: you must handle them. **`try expr`** returns the error to the caller if `expr` fails (like `?` in Rust); **`catch`** handles it (`parse(s) catch 0` provides a default, `catch |err| switch (err) { ... }` handles each case, `catch return error.X` converts); and `if (result) |value| ... else |err| ...` branches on success. Since error sets are known at compile time, a `switch` on an error can be exhaustive. **Optionals** `?T` represent absence: `null` or a value, unwrapped with `orelse` (default), `if (opt) |v|`, or `.?` (assert non-null). **`defer`** runs code when the scope exits, and **`errdefer`** runs it **only if** the scope exits with an error, which is the key tool for cleaning up partially constructed results. In Debug and ReleaseSafe builds, **error return traces** show where an error originated.

## Parsing an order line with error unions

Custom error sets, try, catch with a switch and orelse.

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

const OrderError = error{ EmptyLine, MissingField, InvalidQuantity, QuantityTooLarge };

const OrderLine = struct { sku: []const u8, qty: u16 };

fn parseLine(line: []const u8) OrderError!OrderLine {
    const trimmed = std.mem.trim(u8, line, " \t\r");
    if (trimmed.len == 0) return error.EmptyLine;

    var parts = std.mem.splitScalar(u8, trimmed, ':');
    const sku = parts.next() orelse return error.MissingField;
    const qty_text = parts.next() orelse return error.MissingField;

    const qty = std.fmt.parseInt(u16, qty_text, 10) catch |err| switch (err) {
        error.Overflow => return error.QuantityTooLarge,
        error.InvalidCharacter => return error.InvalidQuantity,
    };
    return .{ .sku = sku, .qty = qty };
}

fn totalQuantity(lines: []const []const u8) OrderError!u32 {
    var total: u32 = 0;
    for (lines) |line| {
        const parsed = try parseLine(line);          // propagate any error to the caller
        total += parsed.qty;
    }
    return total;
}

pub fn main() void {
    const good = [_][]const u8{ "pen:2", "ink:1", "pad:5" };
    const bad = [_][]const u8{ "pen:2", "ink:lots" };

    const t = totalQuantity(&good) catch 0;
    std.debug.print("total qty: {d}\n", .{t});      // 8

    if (totalQuantity(&bad)) |qty| {
        std.debug.print("qty {d}\n", .{qty});
    } else |err| {
        std.debug.print("could not parse order: {s}\n", .{@errorName(err)});   // InvalidQuantity
    }

    const maybe_discount: ?u8 = null;
    std.debug.print("discount: {d}%\n", .{maybe_discount orelse 0});
}
```

## Prefer explicit error sets on public APIs

Inferred error sets (`!T`) are convenient inside a module, but naming the set on public functions documents exactly what callers must handle and keeps exhaustive switches meaningful.

**Quiz:** What does `try foo()` do if foo returns an error?

- [ ] Ignores the error
- [ ] Panics
- [x] Returns that error from the current function immediately
- [ ] Retries foo

*Answer:* Returns that error from the current function immediately. try is shorthand for catching the error and returning it.
