# Idiom for an "old-school" try-catch block

**URL:** <https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821>\
**Category:** Help\
**Tags:** language\
**Created:** [April 4, 2024, 1:48am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821 "2024-04-04T01:48:33Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![biosbob](https://ziggit.dev/user_avatar/ziggit.dev/biosbob/32/1987_2.png) [@biosbob](https://ziggit.dev/u/biosbob)\
**Post date:** [April 4, 2024, 1:48am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/1 "2024-04-04T01:48:33Z")

</div>

i’m finding that i will frequently suffix many library function calls with a `catch unreachable` or maybe a `catch std.zig.fatal(...)`…

back in the day, i would often wrap an entire block of code in a `try { ... } catch(e) { ... }` to keep the noise down on individual function calls that may trigger an error…

is there some way to do this in zig; and if so, is it a “best practice”???

---

<div class="post-metadata">

**Author:** ![AndrewCodeDev](https://ziggit.dev/letter_avatar_proxy/v4/letter/a/278dde/32.png) [@AndrewCodeDev](https://ziggit.dev/u/AndrewCodeDev)\
**Post date:** [April 4, 2024, 2:07am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/2 "2024-04-04T02:07:27Z")

</div>

Edited - added third pattern.

Three patterns for ya… first two are breaking blocks:

In this example, we break the value back to the `result` if it’s an error or a value. We can inspect or use it either way.

```zig
pub fn main() !void {

    const result = blk: {
        // break will return an error here
        const out = bar(42, 42) catch |e| break :blk e;

        // break will return the result
        break :blk out;
    };

    // unpack the error (can use if/else and switch for this too)
    const out = result catch |e| {
        return std.debug.print("caught an error: {}", .{e});
    };

    std.debug.print("Out: {}", .{ out });
}

```

The error can also be discarded with a raw break.

```zig
pub fn main() !void {

    blk: {
        const out = foo(42, 42) catch break :blk;
        // the happy path...
        std.log.info("Result: {}", .{out});        
    }
    // the not-so-happy path...
    std.log.info("Something bad happened.", .{});
}

```

This is closer to the canonical `try/catch` that you’re talking about. You break the block in the same way you’d use a `throw`. The obvious issue with the second one is you lose whatever error you had. It’s really up to you if you care about that or not.

Another pattern to consider is `try/catch |...| return ...` and `catch |...| switch (...)`:

```zig
const MyError = error { A, B, C };

pub fn foo(_: i32, _: i32) MyError!i32 {
    return MyError.A;
}

pub fn bar(_: i32, _: i32) MyError!i32 {
    return MyError.B;
}
pub fn baz(x: i32, y: i32) MyError!i32 {
    // try works as a short hand here
    const u = try foo(x, y);
    // more verbose but can be further customized
    const v = bar(u, x) catch |e| return e;
    return v;
}

pub fn main() !void {

    const out: i32 = baz(42, 42) catch |e| switch (e) {
        MyError.A => return std.log.err("Handle Error A.", .{}),
        MyError.B => return std.log.err("Handle Error B.", .{}),
        MyError.C => return std.log.err("Handle Error C.", .{}),
    };

    std.log.info("Result: {}", .{out});
}

```

This pattern forces the error to go upstream to the call site so you will end up doing your error handling in one place. You’ll notice in the `switch` block, I’m doing returns because out will be considered `void` and we have to evacuate the function if an error occurs.

We had a long discussion about these: [What is the best way to handle unrecoverable errors like OutOfMemory in an application?](https://ziggit.dev/t/what-is-the-best-way-to-handle-unrecoverable-errors-like-outofmemory-in-an-application/3009)

And that was based on this thread: [Zig Code Smells](https://ziggit.dev/t/zig-code-smells/2928/)

---

<div class="post-metadata">

**Author:** ![dude\_the\_builder](https://ziggit.dev/user_avatar/ziggit.dev/dude_the_builder/32/557_2.png) [@dude\_the\_builder](https://ziggit.dev/u/dude_the_builder)\
**Post date:** [April 4, 2024, 2:16am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/3 "2024-04-04T02:16:30Z")

</div>

In the same vein as @AndrewCodeDev’s second pattern, I would say that a simple Zig way to emulate `try`/`catch` would be to put the group of function calls in a wrapper function and then you only `catch` once:

```zig
fn foo() !void {}

fn bar() !void {}

fn baz() !void {
    return error.Boom;
}

fn together() !void {
    try foo();
    try bar();
    try baz();
}

pub fn main() !void {
    together() catch |e| {
        std.debug.print("error: {}\n", .{e});
    };
}

```

---

<div class="post-metadata">

**Author:** ![biosbob](https://ziggit.dev/user_avatar/ziggit.dev/biosbob/32/1987_2.png) [@biosbob](https://ziggit.dev/u/biosbob)\
**Post date:** [April 4, 2024, 2:20am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/4 "2024-04-04T02:20:13Z")

</div>

i was hoping there was some labeled block idiom, which is sort of an unnamed function…

---

<div class="post-metadata">

**Author:** ![AndrewCodeDev](https://ziggit.dev/letter_avatar_proxy/v4/letter/a/278dde/32.png) [@AndrewCodeDev](https://ziggit.dev/u/AndrewCodeDev)\
**Post date:** [April 4, 2024, 2:20am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/5 "2024-04-04T02:20:54Z")

</div>

@biosbob, Also, you can do this as well (forgot to mention):

Here, we break the block and keep the error union and then unpack/switch over it.

```zig
pub fn main() !void {

    const result = blk: {
        // break will return an error here
        const out = bar(42, 42) catch |e| break :blk e;

        // break will return the result
        break :blk out;
    };

    if (result) |out| {
        std.log.info("Out {}", .{ out });
    } else |err| {
        std.log.info("Error: {}", .{err});
    }   
}

```

---

<div class="post-metadata">

**Author:** ![dpirch](https://ziggit.dev/user_avatar/ziggit.dev/dpirch/32/5321_2.png) [@dpirch](https://ziggit.dev/u/dpirch)\
**Post date:** [July 19, 2026, 12:06pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/6 "2026-07-19T12:06:26Z")

</div>

If the block does not return anything other than the possible error, you can also just do

```zig
(blk: {
    mayfail1() catch |err| break :blk err;
    mayfail2() catch |err| break :blk err;
}) catch |err| std.debug.print("handle error: {}\n", .{err});

```

There has been a proposal ([Improve block-local error handling · Issue #5610 · ziglang/zig · GitHub](https://github.com/ziglang/zig/issues/5610)) to add a variant of `try` for leaving labeled blocks, so you could simply do `try :blk mayfail1();` but this was unfortunately rejected, so I guess the `catch |err| break :blk err` remains idiomatic now.

BTW a footgun to be aware of is that returning an error from a block via break does NOT trigger `errdefer`:

```zig
(blk: {
    errdefer std.debug.print("errdefer\n", .{}); // NEVER CALLED!
    mayfail1() catch |err| break :blk err;
}) catch |err| std.debug.print("handle error: {}\n", .{err});

```

---

<div class="post-metadata">

**Author:** ![GigaGrunch](https://ziggit.dev/user_avatar/ziggit.dev/gigagrunch/32/1823_2.png) [@GigaGrunch](https://ziggit.dev/u/GigaGrunch)\
**Post date:** [July 19, 2026, 3:13pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/7 "2026-07-19T15:13:38Z")

</div>

I haven’t actually tried this, but can’t you use errdefer like this as well?

```zig
{
    errdefer |err| switch(err) {
        // handle
    }

    try something();
    const stuff = try getStuff(); 
} 

```

---

<div class="post-metadata">

**Author:** ![00JCIV00](https://ziggit.dev/user_avatar/ziggit.dev/00jciv00/32/1069_2.png) [@00JCIV00](https://ziggit.dev/u/00JCIV00)\
**Post date:** [July 19, 2026, 5:04pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/8 "2026-07-19T17:04:05Z")

</div>

This does work. It’s a pattern I use occasionally when I want to treat all subordinate calls as erroring the same way.

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [July 19, 2026, 5:16pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/9 "2026-07-19T17:16:01Z")

</div>

On master capturing errdefer is already removed.

---

<div class="post-metadata">

**Author:** ![00JCIV00](https://ziggit.dev/user_avatar/ziggit.dev/00jciv00/32/1069_2.png) [@00JCIV00](https://ziggit.dev/u/00JCIV00)\
**Post date:** [July 19, 2026, 6:11pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/10 "2026-07-19T18:11:24Z")

</div>

Good call. I actually don’t capture the error when using this pattern. I only use it when I want to treat all errors as being the same, which is similar to how I used `try/catch` in other languages.

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [July 19, 2026, 6:33pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/11 "2026-07-19T18:33:23Z")

</div>

this sounds like a bug to me!

---

<div class="post-metadata">

**Author:** ![biom4st3r](https://ziggit.dev/user_avatar/ziggit.dev/biom4st3r/32/4342_2.png) [@biom4st3r](https://ziggit.dev/u/biom4st3r)\
**Post date:** [July 19, 2026, 6:40pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/12 "2026-07-19T18:40:20Z")

</div>

There is also

```zig
_ = blk: {
    failing_function0() catch |err| break :blk err;
    failing_function1() catch |err| break :blk err;
    failing_function2() catch |err| break :blk err;
} catch {
    // handle
};

```

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [July 19, 2026, 8:58pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/13 "2026-07-19T20:58:03Z")

</div>

> [@alanza](#):
>
> this sounds like a bug to me!

It seems like at a glance, but I am not so sure. it is just catching the error and assigning its value to a variable, which is a perfectly valid thing to do, not the `errdefer` being triggered by the `try`, which is control flow.

It isn’t uncommon when wrapping C libraries to create functions that convert C error codes into Zig errors, and it isn’t an error to return from them.

```zig
pub const Error = error{
    OutOfMemory,
    InvalidArgument,
};

fn getError(errno: c_int) ?Error {
    return switch (errno) {
        0 => null,
        1 => error.OutOfMemory,
        2 => error.InvalidArgument,
        else => unreachable,
    };
}

pub fn main() !void {
    // No try/catch, though returns an error
    if (getError(0)) |_| {
        unreachable;
    }
    if (getError(1)) |err| {
        std.debug.print("Error name: {s}\n", .{@errorName(err)});
    }
    std.debug.print("Hello world!\n", .{});
}

```

```plaintext
Error name: OutOfMemory
Hello world!

```

EDIT:  
I have given this some more thought, and now I am even less sure. Perhaps this is a bug. The lack of type resolution and which allows for the outer `catch` to be triggered is throwing my mind off.

---

<div class="post-metadata">

**Author:** ![alanza](https://ziggit.dev/user_avatar/ziggit.dev/alanza/32/6006_2.png) [@alanza](https://ziggit.dev/u/alanza)\
**Post date:** [July 19, 2026, 9:37pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/14 "2026-07-19T21:37:08Z")

</div>

The thing which sounds like a bug to me is the `errdefer` not running when the block is exited with an error.

---

<div class="post-metadata">

**Author:** ![ForeverZer0](https://ziggit.dev/user_avatar/ziggit.dev/foreverzer0/32/2443_2.png) [@ForeverZer0](https://ziggit.dev/u/ForeverZer0)\
**Post date:** [July 20, 2026, 1:19am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/15 "2026-07-20T01:19:23Z")

</div>

I was thinking this too, but then the block it is being run within has a `catch` with no return type to infer. My thought was that perhaps the discard is just swallowing it up as a value and not interpreting it as an error condition to trigger the `errdefer`.

The more I think about it the more I lean towards agreeing it might be a bug. It doesn’t seem very well-defined if it isn’t.

---

<div class="post-metadata">

**Author:** ![dupdrop](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/df788c/32.png) [@dupdrop](https://ziggit.dev/u/dupdrop)\
**Post date:** [July 20, 2026, 4:21am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/16 "2026-07-20T04:21:11Z")

</div>

I wonder if adding a scoped-try to Zig is a worthwhile idea. So your example hypothetically could be written something like:

```zig
_ = blk: {
    try :blk failing_function0();
    try :blk failing_function1();
    try :blk failing_function2();
} catch |err| {
    // handle
};

```

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [July 20, 2026, 4:39am UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/17 "2026-07-20T04:39:28Z")

</div>

see above:

> [@dpirch](#):
>
> There has been a proposal ([Improve block-local error handling · Issue #5610 · ziglang/zig · GitHub](https://github.com/ziglang/zig/issues/5610)) to add a variant of `try` for leaving labeled blocks, so you could simply do `try :blk mayfail1();` but this was unfortunately rejected

---

<div class="post-metadata">

**Author:** ![npc1054657282](https://ziggit.dev/user_avatar/ziggit.dev/npc1054657282/32/5790_2.png) [@npc1054657282](https://ziggit.dev/u/npc1054657282)\
**Post date:** [July 20, 2026, 12:17pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/18 "2026-07-20T12:17:15Z")

</div>

> [@dpirch](#):
>
> BTW a footgun to be aware of is that returning an error from a block via break does NOT trigger `errdefer`:
> 
> ```zig
> (blk: {
> errdefer std.debug.print("errdefer\n", .{}); // NEVER CALLED!
> mayfail1() catch |err| break :blk err;
> }) catch |err| std.debug.print("handle error: {}\n", .{err});
> 
> ```

This is totally unexpected for me. I now heavily rely on this way of propagating block-level errors to eliminate duplicate error-handling schemes. If this is true, my code will no longer be reliable.  
I really hope this is just a bug and not a semantic issue.

---

<div class="post-metadata">

**Author:** ![dpirch](https://ziggit.dev/user_avatar/ziggit.dev/dpirch/32/5321_2.png) [@dpirch](https://ziggit.dev/u/dpirch)\
**Post date:** [July 20, 2026, 1:16pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/19 "2026-07-20T13:16:13Z")

</div>

The documentation says that errdefer “evaluates the deferred expression on block exit path if and only if the function returned with an error from the block”; it does just that so it is technically not a bug, because a labeled block is not a function.

But of course if we _think of_ labeled blocks as “manually inlined functions” or “immediately called nested functions”, we expect them to work like functions and `break :label <value>` to be equivalent to returning a value from a function, so it is unexpected that errdefer is not triggered here.

---

<div class="post-metadata">

**Author:** ![dhw9406](https://ziggit.dev/letter_avatar_proxy/v4/letter/d/90db22/32.png) [@dhw9406](https://ziggit.dev/u/dhw9406)\
**Post date:** [July 20, 2026, 1:52pm UTC](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821/20 "2026-07-20T13:52:49Z")

</div>

To add to this, my understanding is that unlike Go’s defer which is tied to function return, Zig’s defer is tied to block scope exit. It would then seem very weird to me that Zig’s errdefer could be tied to function scope.

[Next page](https://ziggit.dev/t/idiom-for-an-old-school-try-catch-block/3821.md?page=2)
