Wrapping and unwrapping errors is now part of Go!

Error Handling in Go 1.13

Keep up with the latest additions in Go error handling

Gett Tech
Published in
5 min readDec 31, 2019

--

This post is part of the “Before you go Go” series, where we explore the world of Golang, provide tips and insights you should know when writing Go, so you don’t have to learn them the hard way.

The last time we visited this topic, we explained some core concepts in this area and how to handle errors properly. I recommend you take a look at the previous post, available here:

This time around, you’re going to update your stance on Error Handling in Go while taking a look at the changes and additions done in Go 1.13. Now that you’ve got all of this introduction out of the way, let’s Go!

Error Wrapping/Unwrapping

Since we covered error handling last time, Go officially integrated support for wrapping in the language in version 1.13, along with a couple of functions to help you with error inspection. Go defines wrapping using an Unwrap() error method:

An error e can wrap another error w by providing an Unwrap method that returns w. Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.

In the last edition of this article, you used errors.Wrap and errors.Cause from the pkg/errors package to wrap and unwrap errors. By using the errors.Wrap function, you’re able to provide additional details (such as stack trace) on your basic error, which is super important for debugging purposes.

This time, let’s go over the latest additions to the fmt package, which now supports the %w verb for fmt.Errorf. The %w verb is only valid for Errorf(), and can only be used once with an error.

Wrapping errors is now supported using the %w verb

Error Inspection

The biggest problem with wrapping errors is probably that it messes with equality comparison and type assertions. The wrapping error may not have the same type as its inner error, and equality checks will fail. To fix that, we’re introduced to a couple of new functions: Unwrap(), Is() and As(). Let’s go over them.

errors.Unwrap

The first function you’ll learn about is Unwrap(err error) error . It serves the same purpose as Cause() we covered last time, but works a bit differently: Unwrap() unwraps errors which satisfy the Unwrap() error interface once, while Cause() extracts the inner-most error of errors which satisfies the Cause() error interface recursively. Both functions return nil in case the error does not support unwrapping.

errors.Is

The Is(err, target) bool function is provided to help with error inspection and replace equality checks. It accepts two arguments and unwraps the first error argument repeatedly, looking for an error that matches its second argument. This way, you’re unwrapping each and every one of those errors, and check for their equivalency.

This is especially helpful when you expect familiar, frequent errors, so you’re able to inspect and check if the error you got is actually one of those. For example, when reading files or executing queries, sentinel errors such asio.EOF, sql.ErrNoRows can be expected, and you should be able to determine if the error you got is one of those — easily.

For these cases, if errors.Is(err, os.ErrExist) is preferable to if err == os.ErrExist. Also, if you should ever need it, you’re able to provide your own Is(err error)bool method for your error and implement your own equality check.

errors.As

As(err error, target interface{}) bool function also unwraps the error repeatedly until it finds a specific error value that matches the target error’s type, and then sets the target to that error.

This serves a similar role to type assertions (it is very common seeing assertions such if e, ok := err.(*os.PathError); ok { … } these days), but works on the entire chain of wrapped errors to find the first one that matches the target’s type, so it is actually preferable.

Wrapping Up

If you’d like to use these changes, but you’re not ready to move to Go 1.13 yet (or you’re using previous versions of Go for some reason), you can use the /x/xerrors package, available here.

It is probably preferable to use errors.Is instead of equality checks, and errors.As instead of a simple type assertion. There are many benefits to both of these functions, and they can lead to simpler, more readable code.

With all that being said, it’s worth mentioning some of the disadvantages of working with error types directly are still relevant to errors.As, and in cases you want to maintain decoupled code, you may want to un-export your error type and provide a simple interface for your users to assert instead.

pkg/errors’ wrapped errors now also support the Unwrap() method, so everything mentioned is also compatible with them when you’re using the latest pkg/errors. Due to their built-in stack trace, I still prefer using it over the fmt.Errorf equivalent.

Now that we’re all wrapped up, thanks for reading! I hope you’ve enjoyed this post, and I’ll see you on the next instalment of the “Before you go Go” series!

--

--

Alon Abadi
Gett Tech

Back End Developer and Golang Enthusiast. I am a sucker for great code, and I like to hack whatever comes my way.