Is `try` really needed?

I recently followed the write a terminal editor tutorial from here: https://viewsourcecode.org/snaptoken/kilo/ in zig 0.16.0 and since there is a lot of writing, which can fail, to stdout() there are A LOT of try keywords in my code (Flashback to .unwrap() and .expect("..") in my rust code). And it got me thinking why exactly do we need try?

We need to deal with the errors anyway why not just automatically raise the error if one occurs (which in reality is what you do in most cases) and have just code like catch for when we deal with it now? This would cleanup the code a lot in my opinion.

Example from:

fn test_method() !void {
  let v1 = try method1();
  try method2();
  try method3();
}

TO:

fn test_method() !void {
  let v1 = method1(); // If error automatically propagates.
  method2() catch |err| {
	  // DEAL WITH ERROR here no auto propagation.
  }
  method3() catch-to result; // Treat result later (Adds bew keyword).
  // result above would be the same error union type as the method return type.
  // Alternatively
  const result !: method3() // !: is like = but cathes the error union.
}

Of course there can be a much better keyword then !: It’s just a placeholder for discussion.

Have a dedicated keyword for when you DON’T want to propagate the error instead of a dedicated keyword for when you DO would really cleanup a lot of code from my point of view.

I think this still hold for the zig zen of communicating intent clearly as you see right there where the error is treated if not you know it bubbles up the chain.

I would like to hear some opinions on this topic from you all.

try is a visual marker for function calls that can fail, and conversely the absence of indicates that a given function call cannot fail. if you make it implicit, then when reading code you will not immediately know if a given function call will cause control flow to change or not, and that would suck.

9 Likes

If you find try annoying then it probably means that you are not handling your errors properly. If you find yourself bubbling all the errors up to main(), then you are no better off than using catch @panic everywhere, which is not a good error handling strategy.
e.g. if reading a file fails and you bubble up the error then the user will just see a completely useless error.FileNotFound or error.AccessDenied as the program crashes.
However if you handle the error locally by e.g. logging the file path together with the error and panicing (or ideally continuing if possible, in a GUI application you could also display an error message box), then the user can go into their file system and fix the file permissions or whatever.

1 Like

If try was to be removed from the language, that would be the end of me using it. :slight_smile:

Most of the time, I don’t need to handle errors at the lower levels, so try is used often. They bubble up, and are handled where needed.

That plus errdefer are one of the main advantages of Zig over Go for me.

2 Likes

zig always uses keywords for control flow, this would violate that consistency.

This is better, but it still inverts the relationship of keywords and controlflow, there is no existing control flow that does this.

how do you tell if a function does not error? (without reading its definition)

2 Likes