`try` for a block, not a function

I need this all the time and I’m aware of the current recommended pattern:

(err: {
    const foo = run() catch |err| break :err err;
    const bar = func(foo) catch |err| break :err err;
    return bar.value;
}) catch |err| {
    // ...
};

but it’s quite wordy and difficult to read and my code always ends up running off the right side of the screen. Has anyone proposed this yet?

(err: {
    const foo = try :err run();
    const bar = try :err func(foo);
    return bar.value;
}) catch |err| {
    // ...
};

Why was it rejected?

I’d like to see regular try blocks, but I guess they are even more anathema than your proposal :wink: E.g.

try {
    const foo = run();
    const bar = func(foo);
    return bar.value;
} catch |err| {
    // ...
}

…e.g. this would be syntax sugar for putting a try in front of each function that has an error union return value.

I might miss some subtle incompatibilities with the Zig syntax or philosophy though :slight_smile:

2 Likes

not for nothing, but i think the Zig Zen way is just

fn fallible() !Value {
    const foo = try run();
    const bar = try func(foo);
    return bar.value;
}

// ...
    const value = fallible() catch |err| {
    // ...
    };
// ...
7 Likes

I think this pattern isn’t compatible with current syntax. A {} block is a statement that evaluates to something and I don’t see what this something would be here. try is used to unwrap an error union to a value or return, but if the error union is already unwrapped what does the catch catch?
What a try block is supposed to express also isn’t equivalent to this:

const foo = try run();
const bar = try func(foo);
return bar.value;

which wouldn’t give the user a chance to handle err in place and would just return it to the caller, but rather to this:

const foo = run() catch |err| handle(err);
const bar = func(foo) catch |err| handle(err);
return bar.value;

which explicitly doesn’t use try anywhere because it doesn’t just return err from the function and handle()s it some other way. So I think using try {} here doesn’t really make sense, it implies that err is just returned.

I really like this suggestion, the ability to give try a scope would be very useful I think. Also it makes more sense syntactically IMO.

Although maybe there’s more value in keeping return and try which deal with functions separate from break, continue etc. which deal with blocks.

Relevant issue:

2 Likes

Raku CATCH block syntax would be even more clearer:

{
	a();
	catch |err| {
		c();
	}
	try b();
}

Which, in most other programming languages, translates to:

{
	a();
	try {
		b():
	} catch |err| {
		c();
	}
}

Proposal: introduce errbreak

Works just like break but only executes if an error occurs, analogous to defer and errdefer.
It would only be usable inside a block of any kind as an alternative to try and would unwrap error unions.

Instead of:

blk: {
    var foo = bar() catch |err| break :blk err;
    foo.baz() catch |err| break :blk err;
    return foo;
} catch |err| {...}

you could write:

blk: {
    var foo = errbreak :blk bar();
    errbreak :blk foo.baz();
    return foo;
} catch |err| {...}

This is similiar to try :blk from this thread and try_local from #5610 but I think the name is more clear.

Thanks for the link! I see my idea was already proposed here and not officially rejected, but it’s been 5 years of silence so I don’t have a lot of hope.

That first block is perfectly valid in current Zig, isn’t it? Honestly I haven’t found myself wishing I had a block catch yet, because I usually just try a bunch of different function calls, and then catch+switch at the call site if necessary to handle it all. If I wanted to do immediate error handling of a block like this though, this seems like a reasonable solution using existing syntax.

Actually it doesn’t compile because zig can’t infer the type of blk I think but I’ve used this pattern in the past to implement C callbacks where the only error handling I could really do was logging the error and returning 1 like this:

[...]
    const a = blk: {
        const b = foo() catch |err| break :blk err;
        [...]
        break :blk b;
    } catch |err| {
        log.err("{s}\n", .{@errorName(err)});
        return 1;
    };
[...]

Something like errbreak or whatever would save a couple of keystrokes here. But using this pattern is certainly already possible with existing syntax.