Don't Return Err in Go

  • > Proponents of exceptions may say, “this is so much manual writing labor, exception stack trace would automate that!”

    Yes indeed.

    > by writing custom messages, we can provide more details useful for debugging (for example, the os.ReadFile helped us with a filename);

    Custom exceptions anyone ?

    > by not relying on code line numbers, the messages are more long-lived, and can actually be understood and reasoned about in isolation, without access to the source code.

    Doubt...

    I quite like Go (some aspects of it anyway) and it's proven both useful and successful, but some people will go great length to find virtues in its weaknesses.

  • Golang error handling is terrible by default. The lack of sum types also make it hard to know what errors a function might return and libraries rarely wrap their errors. There also isn’t any stack trace by default which makes it hard to find the original source of the error.

    Check out https://github.com/rotisserie/eris. It really is a lifesaver

  • This is good advice. I had a hard time with this aspect of go until wrapping clicked. I think (as mentioned in the post) creating custom errors can be extremely useful as well, in moderation. Go error handling is really not that bad if you embrace it and use it well.

    Like many things in go, I think a lot of us unwittingly hope it will behave in familiar ways because so much of the language seems simple or intuitive. Unfortunately, without a good grasp on it, many parts of go will appear to cooperate with your hopes until it doesn’t.

    I think a great example of this is the type system. There is a correct way to use it, but it’ll let you misuse it quite extensively before things start to implode. Errors are a bit like this. You can return raw errors for a long time and it’ll seem fine until suddenly, debugging and tracing issues is incredibly inconvenient and unintuitive.

    I found the book “Learning Go” really helpful in breaking these bad habits and helping me get acquainted with how things actually work under the hood.

  • Given how pointedly opinionated Go is about how codebases should look like (unused variables are a compiler error!?), I'm somewhat surprised that `if err != nil return err` isn't some kind of built in warning. I do think Go as a language encourages this bad behavior.

  • As a TS dev, I found that this type of checking will blow up a codebase to the point where it becomes significantly harder to read. Worse, I've seen a good developer spend way too much thought and code on correctly reporting cases that realistically won't even ever happen. Since TS has stack traces, I find it much more practical to handle based on broader categories: Errors for users need some system for displaying messages, icons etc.. Errors for admins need similar handling, but it makes little sense to spend this much effort on pretty error handling when the error is for a developer.

  • Error wrapping is by far my favorite go language feature.

  • I built a drop-in replacement to `error` recently. Instead of manipulating strings, it adds context, stacktrace... in a structured way.

    https://github.com/samber/oops

  • For small codebases it might work to just grep and hope to find unique error messages. But it feels very brittle. Go should have an option to turn on stacktraces for all errors.

    We wrap errors with stacktraces at library boundaries so that we can `return err` in most cases, unless we really want to add some information. But mostly we use sentry to push log messages with better data into breadcrumbs leading to the errors.

  • What I see wrong with Go's error handling is that the error type is an open set which welcomes you with a Pandora's box full of infinite possible error values.

    This is much too burdensome for me to cognitively bear so I prefer a closed set model as seen in Zig, Rust, or any ML.

    I don't recall if Swift has exhaustive error handling.

  • Is this poor man's stacktrace?

  • If you have any architectural complexity in your program, good error handling often come with custom errors. Unique error strings just don't cut it, you need a better structured type than a string to apply any kind of logic.

  • Still not seeing how this saves you from having to check if err != nil everywhere.

    Though I suppose if this were another language it would be akin to a try catch around every line of code. Which in a way is great, but pretty labor intensive.

  • Why do it feel like everyone is copying rust

  • Great advice!

  • tl;dr return wrapped error instead