Live data from Hacker News

Structured Errors in Go (2022)

southcla.ws

21–30 of 75 posts

Re: Structured Errors in Go (2022)

#21
post #3

One thing that seemingly is missing is the ability to tag a specific error with an error code. You typically want to know that all of a sudden the ”failed to get user” error is being returned a lot. Since the message is a dynamic string you can’t just group by the string so unless you build it as part of your abstraction it becomes very hard to do. Edit: looking more carefully at the lib I assume that ”tag” is the co…

in standard go you'd have errors implement:

    func (myError) Is(err error) bool
and it can match different sentinel errors. Or you can make your own wrapper to have the error chain match.

Re: Structured Errors in Go (2022)

#22
post #9

The implementation of WithMeta() is flawed. Not only is it not concurrency-safe, every nested call will be modifying the parent map. The way to do this in a safe and performant manner is to structure the metadata as a tree, with a parent pointing to the previous metadata. You'd probably want to do some pooling and other optimizations to avoid allocating a map every time. Then all the maps can be immutable and therefo…

I think this is the way to bubble up error messages that I like the most. Simple, not needing any additional tools, and very practical (sometimes even better than a stack trace). The idea is to only add information that the caller isn't already aware of . Error messages shouldn't include the function name or any of its arguments, because the caller will include those in its own wrapping of that error. This is done wi…

I agree except for the part about using strings, as you lose structure they way. You should instead return structured errors:

    return CouldNotStoreUser{
      UserID: userId,
    }
and now this struct is available to anyone looking up the chain of wrapped errors.

Re: Structured Errors in Go (2022)

#23
post #9

Earlier quoted context omitted.

I think this is the way to bubble up error messages that I like the most. Simple, not needing any additional tools, and very practical (sometimes even better than a stack trace). The idea is to only add information that the caller isn't already aware of . Error messages shouldn't include the function name or any of its arguments, because the caller will include those in its own wrapping of that error. This is done wi…

I agree except for the part about using strings, as you lose structure they way. You should instead return structured errors: return CouldNotStoreUser{ UserID: userId, } and now this struct is available to anyone looking up the chain of wrapped errors.

This is covered by the “ergonomics” section of TFA:

> With custom error structs however, it's a lot of writing to create your own error type and thus it becomes more of a burden to encourage your team members to do this.

Because you need a type per layer, and that type needs to implement both error and unwrap.

Re: Structured Errors in Go (2022)

#24

Earlier quoted context omitted.

I agree except for the part about using strings, as you lose structure they way. You should instead return structured errors: return CouldNotStoreUser{ UserID: userId, } and now this struct is available to anyone looking up the chain of wrapped errors.

This is covered by the “ergonomics” section of TFA: > With custom error structs however, it's a lot of writing to create your own error type and thus it becomes more of a burden to encourage your team members to do this. Because you need a type per layer, and that type needs to implement both error and unwrap.

In practice, I have found the benefit of explicit typing to outweigh the downsides of needing to declare the types.

As a concrete example, it means you can target types with precision in the API layer:

    switch e := err.(type) {
      case UserNotFound:
        writeJSONResponse(w, 404, "User not found")
      case interface { Timeout() bool }:
        if e.Timeout() {
          writeJSONResponse(w, 503, "Timeout")
        }
    }
I skimmed the article and didn't see the author proposing a way to do that with their arbitrary key/value map.

Of course, you could use something else like error codes to translate groups of errors. But then why not just use types?

But as I suggested in my other comment, you could also generalize it. For example:

    return meta.Wrap(err, "storing user", "userID", userID)
Here, Wrap() is something like:

    func Wrap(err error, msg string, kvs ...any) {
      return &KV{
        KV:    kvs,
        cause: err,
        msg:   msg,
      }
    }
This is the inverse of the context solution. The point is to provide data at the point of error, not at every call site.

You can always merge these later into a single map and pay the allocation cost there:

    var fields map[string]any
    for err != nil {
      if e, ok := err.(*KV); ok {
        for i := 0; i 

Re: Structured Errors in Go (2022)

#27
post #9

The implementation of WithMeta() is flawed. Not only is it not concurrency-safe, every nested call will be modifying the parent map. The way to do this in a safe and performant manner is to structure the metadata as a tree, with a parent pointing to the previous metadata. You'd probably want to do some pooling and other optimizations to avoid allocating a map every time. Then all the maps can be immutable and therefo…

I think this is the way to bubble up error messages that I like the most. Simple, not needing any additional tools, and very practical (sometimes even better than a stack trace). The idea is to only add information that the caller isn't already aware of . Error messages shouldn't include the function name or any of its arguments, because the caller will include those in its own wrapping of that error. This is done wi…

You have to add all of that detail manually which sucks. You can get the function name from the runtime package and generate that metadata easily with a helper function. Otherwise when you rename the function, you have to rename all of the error messages.

Re: Structured Errors in Go (2022)

#28
post #10

The implementation of WithMeta() is flawed. Not only is it not concurrency-safe, every nested call will be modifying the parent map. The way to do this in a safe and performant manner is to structure the metadata as a tree, with a parent pointing to the previous metadata. You'd probably want to do some pooling and other optimizations to avoid allocating a map every time. Then all the maps can be immutable and therefo…

Just a little more work, and you will reinvent exceptions with stack traces! Proper error handling in Go is now tantalisingly close!

Go already has exceptions with stack traces...

Re: Structured Errors in Go (2022)

#29
post #8

Not a Go engineer but Go-curious - shouldn’t this use slog[0] for structured logging rather than a third party?

Depends on if you're making a public library. Using slog is "polite" but I don't really see it as the endgame of logging libraries. It has quite a few rough edges for CLI apps, like no control on attr order. But it is zomgfast, and speed is important, right?

Re: Structured Errors in Go (2022)

#30
post #13
post #2

Yeah always thought error handling is a bit wonky in Go. (Un)fortunately, most of my tinkering with Go are just toy level scripts. Thanks for the write up, will check the library!

Go itself is wonky, yet another programming language that is a fine example of worse is better mentality in the industry, whose adoption was helped by having critical infrastructure software written in it.

> whose adoption was helped by having critical infrastructure software written in it

Doesn't this contradict the first part of your post? Kubernetes for instance was ported from Java to Go (albeit, poorly). Is Java worse than Go?

Post reply on HN