| # Working with Errors in Go 1.13 | |
| 17 Oct 2019 | |
| Tags: errors, technical | |
| Summary: How to use the new Go 1.13 error interfaces and functions. | |
| Damien Neil and Jonathan Amsterdam | |
| ## Introduction | |
| Go’s treatment of [errors as values](https://blog.golang.org/errors-are-values) | |
| has served us well over the last decade. Although the standard library’s support | |
| for errors has been minimal—just the `errors.New` and `fmt.Errorf` functions, | |
| which produce errors that contain only a message—the built-in `error` interface | |
| allows Go programmers to add whatever information they desire. All it requires | |
| is a type that implements an `Error` method: | |
| type QueryError struct { | |
| Query string | |
| Err error | |
| } | |
| func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } | |
| Error types like this one are ubiquitous, and the information they store varies | |
| widely, from timestamps to filenames to server addresses. Often, that | |
| information includes another, lower-level error to provide additional context. | |
| The pattern of one error containing another is so pervasive in Go code that, | |
| after [extensive discussion](https://golang.org/issue/29934), Go 1.13 added | |
| explicit support for it. This post describes the additions to the standard | |
| library that provide that support: three new functions in the `errors` package, | |
| and a new formatting verb for `fmt.Errorf`. | |
| Before describing the changes in detail, let's review how errors are examined | |
| and constructed in previous versions of the language. | |
| ## Errors before Go 1.13 | |
| ### Examining errors | |
| Go errors are values. Programs make decisions based on those values in a few | |
| ways. The most common is to compare an error to `nil` to see if an operation | |
| failed. | |
| if err != nil { | |
| // something went wrong | |
| } | |
| Sometimes we compare an error to a known _sentinel_ value, to see if a specific error has occurred. | |
| var ErrNotFound = errors.New("not found") | |
| if err == ErrNotFound { | |
| // something wasn't found | |
| } | |
| An error value may be of any type which satisfies the language-defined `error` | |
| interface. A program can use a type assertion or type switch to view an error | |
| value as a more specific type. | |
| type NotFoundError struct { | |
| Name string | |
| } | |
| func (e *NotFoundError) Error() string { return e.Name + ": not found" } | |
| if e, ok := err.(*NotFoundError); ok { | |
| // e.Name wasn't found | |
| } | |
| ### Adding information | |
| Frequently a function passes an error up the call stack while adding information | |
| to it, like a brief description of what was happening when the error occurred. A | |
| simple way to do this is to construct a new error that includes the text of the | |
| previous one: | |
| if err != nil { | |
| return fmt.Errorf("decompress %v: %v", name, err) | |
| } | |
| Creating a new error with `fmt.Errorf` discards everything from the original | |
| error except the text. As we saw above with `QueryError`, we may sometimes want | |
| to define a new error type that contains the underlying error, preserving it for | |
| inspection by code. Here is `QueryError` again: | |
| type QueryError struct { | |
| Query string | |
| Err error | |
| } | |
| Programs can look inside a `*QueryError` value to make decisions based on the | |
| underlying error. You'll sometimes see this referred to as "unwrapping" the | |
| error. | |
| if e, ok := err.(*QueryError); ok && e.Err == ErrPermission { | |
| // query failed because of a permission problem | |
| } | |
| The `os.PathError` type in the standard library is another example of one error which contains another. | |
| ## Errors in Go 1.13 | |
| ### The Unwrap method | |
| Go 1.13 introduces new features to the `errors` and `fmt` standard library | |
| packages to simplify working with errors that contain other errors. The most | |
| significant of these is a convention rather than a change: an error which | |
| contains another may implement an `Unwrap` method returning the underlying | |
| error. If `e1.Unwrap()` returns `e2`, then we say that `e1` _wraps_ `e2`, and | |
| that you can _unwrap_ `e1` to get `e2`. | |
| Following this convention, we can give the `QueryError` type above an `Unwrap` | |
| method that returns its contained error: | |
| func (e *QueryError) Unwrap() error { return e.Err } | |
| The result of unwrapping an error may itself have an `Unwrap` method; we call | |
| the sequence of errors produced by repeated unwrapping the _error chain_. | |
| ### Examining errors with Is and As | |
| The Go 1.13 `errors` package includes two new functions for examining errors: `Is` and `As`. | |
| The `errors.Is` function compares an error to a value. | |
| // Similar to: | |
| // if err == ErrNotFound { … } | |
| if errors.Is(err, ErrNotFound) { | |
| // something wasn't found | |
| } | |
| The `As` function tests whether an error is a specific type. | |
| // Similar to: | |
| // if e, ok := err.(*QueryError); ok { … } | |
| var e *QueryError | |
| if errors.As(err, &e) { | |
| // err is a *QueryError, and e is set to the error's value | |
| } | |
| In the simplest case, the `errors.Is` function behaves like a comparison to a | |
| sentinel error, and the `errors.As` function behaves like a type assertion. When | |
| operating on wrapped errors, however, these functions consider all the errors in | |
| a chain. Let's look again at the example from above of unwrapping a `QueryError` | |
| to examine the underlying error: | |
| if e, ok := err.(*QueryError); ok && e.Err == ErrPermission { | |
| // query failed because of a permission problem | |
| } | |
| Using the `errors.Is` function, we can write this as: | |
| if errors.Is(err, ErrPermission) { | |
| // err, or some error that it wraps, is a permission problem | |
| } | |
| The `errors` package also includes a new `Unwrap` function which returns the | |
| result of calling an error's `Unwrap` method, or `nil` when the error has no | |
| `Unwrap` method. It is usually better to use `errors.Is` or `errors.As`, | |
| however, since these functions will examine the entire chain in a single call. | |
| ### Wrapping errors with %w | |
| As mentioned earlier, it is common to use the `fmt.Errorf` function to add additional information to an error. | |
| if err != nil { | |
| return fmt.Errorf("decompress %v: %v", name, err) | |
| } | |
| In Go 1.13, the `fmt.Errorf` function supports a new `%w` verb. When this verb | |
| is present, the error returned by `fmt.Errorf` will have an `Unwrap` method | |
| returning the argument of `%w`, which must be an error. In all other ways, `%w` | |
| is identical to `%v`. | |
| if err != nil { | |
| // Return an error which unwraps to err. | |
| return fmt.Errorf("decompress %v: %w", name, err) | |
| } | |
| Wrapping an error with `%w` makes it available to `errors.Is` and `errors.As`: | |
| err := fmt.Errorf("access denied: %w", ErrPermission) | |
| ... | |
| if errors.Is(err, ErrPermission) ... | |
| ### Whether to Wrap | |
| When adding additional context to an error, either with `fmt.Errorf` or by | |
| implementing a custom type, you need to decide whether the new error should wrap | |
| the original. There is no single answer to this question; it depends on the | |
| context in which the new error is created. Wrap an error to expose it to | |
| callers. Do not wrap an error when doing so would expose implementation details. | |
| As one example, imagine a `Parse` function which reads a complex data structure | |
| from an `io.Reader`. If an error occurs, we wish to report the line and column | |
| number at which it occurred. If the error occurs while reading from the | |
| `io.Reader`, we will want to wrap that error to allow inspection of the | |
| underlying problem. Since the caller provided the `io.Reader` to the function, | |
| it makes sense to expose the error produced by it. | |
| In contrast, a function which makes several calls to a database probably should | |
| not return an error which unwraps to the result of one of those calls. If the | |
| database used by the function is an implementation detail, then exposing these | |
| errors is a violation of abstraction. For example, if the `LookupUser` function | |
| of your package `pkg` uses Go's `database/sql` package, then it may encounter a | |
| `sql.ErrNoRows` error. If you return that error with | |
| `fmt.Errorf("accessing DB: %v", err)` | |
| then a caller cannot look inside to find the `sql.ErrNoRows`. But if | |
| the function instead returns `fmt.Errorf("accessing DB: %w", err)`, then a | |
| caller could reasonably write | |
| err := pkg.LookupUser(...) | |
| if errors.Is(err, sql.ErrNoRows) … | |
| At that point, the function must always return `sql.ErrNoRows` if you don't want | |
| to break your clients, even if you switch to a different database package. In | |
| other words, wrapping an error makes that error part of your API. If you don't | |
| want to commit to supporting that error as part of your API in the future, you | |
| shouldn't wrap the error. | |
| It’s important to remember that whether you wrap or not, the error text will be | |
| the same. A _person_ trying to understand the error will have the same information | |
| either way; the choice to wrap is about whether to give _programs_ additional | |
| information so they can make more informed decisions, or to withhold that | |
| information to preserve an abstraction layer. | |
| ## Customizing error tests with Is and As methods | |
| The `errors.Is` function examines each error in a chain for a match with a | |
| target value. By default, an error matches the target if the two are equal. In | |
| addition, an error in the chain may declare that it matches a target by | |
| implementing an `Is` _method_. | |
| As an example, consider this error inspired by the | |
| [Upspin error package](https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html) | |
| which compares an error against a template, considering only fields which are | |
| non-zero in the template: | |
| type Error struct { | |
| Path string | |
| User string | |
| } | |
| func (e *Error) Is(target error) bool { | |
| t, ok := target.(*Error) | |
| if !ok { | |
| return false | |
| } | |
| return (e.Path == t.Path || t.Path == "") && | |
| (e.User == t.User || t.User == "") | |
| } | |
| if errors.Is(err, &Error{User: "someuser"}) { | |
| // err's User field is "someuser". | |
| } | |
| The `errors.As` function similarly consults an `As` method when present. | |
| ## Errors and package APIs | |
| A package which returns errors (and most do) should describe what properties of | |
| those errors programmers may rely on. A well-designed package will also avoid | |
| returning errors with properties that should not be relied upon. | |
| The simplest specification is to say that operations either succeed or fail, | |
| returning a nil or non-nil error value respectively. In many cases, no further | |
| information is needed. | |
| If we wish a function to return an identifiable error condition, such as "item | |
| not found," we might return an error wrapping a sentinel. | |
| var ErrNotFound = errors.New("not found") | |
| // FetchItem returns the named item. | |
| // | |
| // If no item with the name exists, FetchItem returns an error | |
| // wrapping ErrNotFound. | |
| func FetchItem(name string) (*Item, error) { | |
| if itemNotFound(name) { | |
| return nil, fmt.Errorf("%q: %w", name, ErrNotFound) | |
| } | |
| // ... | |
| } | |
| There are other existing patterns for providing errors which can be semantically | |
| examined by the caller, such as directly returning a sentinel value, a specific | |
| type, or a value which can be examined with a predicate function. | |
| In all cases, care should be taken not to expose internal details to the user. | |
| As we touched on in "Whether to Wrap" above, when you return | |
| an error from another package you should convert the error to a form that does | |
| not expose the underlying error, unless you are willing to commit to returning | |
| that specific error in the future. | |
| f, err := os.Open(filename) | |
| if err != nil { | |
| // The *os.PathError returned by os.Open is an internal detail. | |
| // To avoid exposing it to the caller, repackage it as a new | |
| // error with the same text. We use the %v formatting verb, since | |
| // %w would permit the caller to unwrap the original *os.PathError. | |
| return fmt.Errorf("%v", err) | |
| } | |
| If a function is defined as returning an error wrapping some sentinel or type, | |
| do not return the underlying error directly. | |
| var ErrPermission = errors.New("permission denied") | |
| // DoSomething returns an error wrapping ErrPermission if the user | |
| // does not have permission to do something. | |
| func DoSomething() error { | |
| if !userHasPermission() { | |
| // If we return ErrPermission directly, callers might come | |
| // to depend on the exact error value, writing code like this: | |
| // | |
| // if err := pkg.DoSomething(); err == pkg.ErrPermission { … } | |
| // | |
| // This will cause problems if we want to add additional | |
| // context to the error in the future. To avoid this, we | |
| // return an error wrapping the sentinel so that users must | |
| // always unwrap it: | |
| // | |
| // if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... } | |
| return fmt.Errorf("%w", ErrPermission) | |
| } | |
| // ... | |
| } | |
| ## Conclusion | |
| Although the changes we’ve discussed amount to just three functions and a | |
| formatting verb, we hope they will go a long way toward improving how errors are | |
| handled in Go programs. We expect that wrapping to provide additional context | |
| will become commonplace, helping programs to make better decisions and helping | |
| programmers to find bugs more quickly. | |
| As Russ Cox said in his [GopherCon 2019 keynote](https://blog.golang.org/experiment), | |
| on the path to Go 2 we experiment, simplify and ship. Now that we’ve | |
| shipped these changes, we look forward to the experiments that will follow. |