Hacker News new | ask | show | jobs
by the_duke 2552 days ago
This looks pretty much exactly like the old try!(expr) macro in Rust, which has since been replaced with the postfix `?` operator.

One crucial difference is that the proposal uses a named `err` return value that can be manipulated in defer. This is supposed to allow cleanup and wrapping of the error type.

Rust solves wrapping is by allowing auto-conversion between error types (if they implement it).

My first intuition is that it would be an improvement over omni-present `if err != nil {}`, but it feels somewhat awkward and tacked on. Especially the mutable `err` return value.

Of course there also was a reason why try! was replaced with `?`: awkward nesting and chaining. Go would have the same problem.

3 comments

> Of course there also was a reason why try! was replaced with `?`: awkward nesting and chaining. Go would have the same problem.

Thanks to tuple returns being a bolted-on afterthought on the language, Go already features awkward and cumbersome nesting and chaining!

To be clear, you can name the return value whatever you want. People just typically name their error values `err`. Also, the mechanisms to mess with named return values in defer already exist: https://play.golang.org/p/7nFyiuAa3Ra
Just to clarify, I was aware that named returns already exist in Go.

My main point is that this somewhat weird pattern is encouraged by the proposal since no other wrapping mechanism exists. And IMO wrapping is really essential for debuggable code.

I totally agree that good error wrapping is essential. In fact, I've been using this exact system for wrapping my errors for years, and it's been great (https://godoc.org/github.com/zeebo/errs#WrapP).

I suspect it's only "somewhat weird" due to lack of familiarity, and that adding an additional mechanism to wrap errors when one already exists is not in the spirit of a language built from small orthogonal components.

I don't like that it's mutable state that is easy to shadow accidentally. (tooling can warn you about it, but it's suboptimal).

Also, in a function with a couple of different error types, do you end up checking the error type manually and reacting accordingly, all in one final defer? That seems error prone. And it doesn't work at all if multiple statements can produce the same error - you won't know which statement caused it.

And, last but not least, this would loose proper backtrace information: the backtraces all point to the defer line rather than separate lines for each error.

You can’t naked return with a shadowed named return variable. The compiler disallows it. Thus, at any return site, you locally know that you’re either returning the only err in scope (the named return), or the specific value listed in the return will be assigned to the named return variable.

I don’t know what error types has to do with it. If you need to annotate differently for every exit point, sure, a defer doesn’t work, but neither would any other proposals I’ve seen. In those cases, do the more verbose thing because the verbosity is apparently warranted. In my experience, it is rarely warranted, and a stack trace with annotation about the package/general operation is sufficient.

The stack traces contained still include what line the return happened on when queried from inside the defer. They retain the information about which return executed. My, or any, helper could, if desired, explicitly skip the defer stack frame, and it would be indistinguishable from capturing at the return itself.

> I don't like that it's mutable state that is easy to shadow accidentally.

It can't happen. The compiler forbids it: https://play.golang.org/p/65bFHrgGblb

> Also, in a function with a couple of different error types, do you end up checking the error type manually and reacting accordingly, all in one final defer?

If a function needs to check the error returned by another function and act accordingly, then don't use try() and use a if statement.

If we just need to decorate the errors with proper context before returning them to the caller, then we use try everywhere and a single defer statement for the decoration. We can use a single defer statement because we expect that the error context is the same in the whole function. See this comment by Russ Cox: https://github.com/golang/go/issues/32437#issuecomment-50329...

> this would loose proper backtrace information: the backtraces all point to the defer line rather than separate lines for each error

No. In the deferred function, you can get the line of the actual return: https://play.golang.org/p/7MVZupCLh5F

In Rust you can also do arbitrary operations on the error before it is returned with `Result.map_err()`.