# `try`

### `try`

`try` is the most common way to handle errors in Zig.

It means:

```text
call this function;
if it succeeds, give me the success value;
if it fails, return the error from the current function
```

So `try` does two jobs:

It unwraps the success value.

It propagates the error upward.

### The Basic Shape

Suppose we have this function:

```zig
const ParseError = error{
    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
```

That means:

```text
either a ParseError,
or a u8
```

Now call it with `try`:

```zig
fn parseTwoDigits(a: u8, b: u8) ParseError!u8 {
    const first = try parseDigit(a);
    const second = try parseDigit(b);

    return first * 10 + second;
}
```

This line:

```zig
const first = try parseDigit(a);
```

means:

```text
if parseDigit(a) succeeds, store the u8 in first
if parseDigit(a) fails, return that error from parseTwoDigits
```

After `try`, `first` is a plain `u8`. It is no longer an error union.

### `try` Requires the Current Function to Return an Error

This is important.

Because `try` may return an error from the current function, the current function must be allowed to return errors.

This works:

```zig
fn run() !void {
    try doWork();
}
```

This does not work:

```zig
fn run() void {
    try doWork();
}
```

The second version says `run` returns `void`, not `!void`. So where would the error go?

If a function uses `try`, its return type usually needs `!`.

```zig
fn run() !void {
    try doWork();
}
```

Read this as:

```text
run either succeeds with no value,
or returns an error
```

### `try` Is Not Magic

`try` is short syntax for a common `catch` pattern.

This:

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

means roughly:

```zig
const value = parseDigit(c) catch |err| return err;
```

So `try` does not hide error handling. It expresses a common rule:

```text
I do not handle this error here.
Send it to my caller.
```

This is useful because many functions are not the right place to decide what to do about an error.

For example, a low-level file-reading function should not always print an error message or exit the program. It should often return the error to a higher-level function that knows the context.

### A File Example

Here is a function that reads a file into memory:

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

fn readTextFile(
    allocator: std.mem.Allocator,
    path: []const u8,
) ![]u8 {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();

    return try file.readToEndAlloc(allocator, 1024 * 1024);
}
```

There are two `try` expressions.

```zig
const file = try std.fs.cwd().openFile(path, .{});
```

Opening the file can fail. The file may not exist. The program may not have permission. The path may be invalid.

If opening succeeds, `file` becomes a normal file value.

If opening fails, the function returns the error immediately.

The second `try` is here:

```zig
return try file.readToEndAlloc(allocator, 1024 * 1024);
```

Reading can also fail. Allocation can fail. The file operation can fail.

If it succeeds, the function returns the byte slice.

If it fails, the function returns the error.

### `try` Preserves the Original Error

When `try` propagates an error, it sends the same error upward.

Example:

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

fn outer() !void {
    try inner();
}
```

If `inner` returns `error.NotFound`, then `outer` also returns `error.NotFound`.

The error is not converted automatically into a string or exception object. It remains an error value.

### Error Sets Must Fit

When you use `try`, the error from the called function must fit into the error set of the current function.

This works:

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

const OuterError = error{
    NotFound,
    PermissionDenied,
};

fn inner() InnerError!void {
    return error.NotFound;
}

fn outer() OuterError!void {
    try inner();
}
```

`inner` can return `NotFound`.

`outer` can also return `NotFound`.

So `try inner();` is valid.

But this does not work:

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

const OuterError = error{
    NotFound,
};

fn inner() InnerError!void {
    return error.PermissionDenied;
}

fn outer() OuterError!void {
    try inner();
}
```

`inner` may return `PermissionDenied`, but `outer` only promises `NotFound`.

Zig rejects that because `outer` would be returning an error outside its declared contract.

You can fix it by widening the outer error set:

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

Or by handling the extra error locally with `catch`.

### `try` Can Be Used Inside Expressions

`try` is an expression. That means it can appear where a value is needed.

Example:

```zig
const total = (try parseDigit('4')) + (try parseDigit('5'));
```

This works because each `try parseDigit(...)` produces a `u8` if it succeeds.

But for readability, beginners should usually split this into separate lines:

```zig
const a = try parseDigit('4');
const b = try parseDigit('5');
const total = a + b;
```

This is easier to debug and easier to explain.

### `try` with `void`

Many error-returning functions have success type `void`.

```zig
fn save() !void {
    // may fail
}
```

Calling this with `try` looks like this:

```zig
try save();
```

There is no value to store. If `save` succeeds, execution continues. If it fails, the error is returned from the current function.

This pattern is common:

```zig
fn run() !void {
    try connect();
    try sendRequest();
    try readResponse();
}
```

Read it as a sequence of steps.

If any step fails, `run` stops and returns the error.

### `try` and Cleanup

When `try` returns early, `defer` still runs.

Example:

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

    try processFile(file);
}
```

If `processFile(file)` fails, the function returns early.

But before it returns, this still runs:

```zig
defer file.close();
```

That is why `try` works well with `defer`. You can write cleanup once, then allow errors to propagate safely.

### `try` Should Not Replace Thinking

`try` is convenient, but you should not use it blindly.

Use `try` when the current function cannot make a good decision about the error.

Handle the error locally when the current function does know what to do.

For example, this is a good use of `try`:

```zig
fn loadConfig(allocator: std.mem.Allocator) !Config {
    const text = try readTextFile(allocator, "config.json");
    defer allocator.free(text);

    return try parseConfig(text);
}
```

`loadConfig` does not know how the whole application wants to respond to failure. It returns the error upward.

But this may be better with `catch`:

```zig
const port = parsePort(text) catch 8080;
```

Here, the program has a clear fallback. If the port is invalid, use a default.

### The Core Idea

`try` means:

```text
unwrap the success value,
or return the error from this function
```

It is Zig’s clean syntax for error propagation.

Use `try` when the current function should not handle the error itself. Use `catch` when the current function should decide what happens next.

