Error Handling in Go 1.13
Keep up with the latest additions in Go error handling
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 errorw
by providing anUnwrap
method that returnsw
. Bothe
andw
are available to programs, allowinge
to provide additional context tow
or to reinterpret it while still allowing programs to make decisions based onw
.
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.
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!