blob: c912aae3144608db6ee78e3225be754fd6a460fd [file] [log] [blame] [view]
Jonathan Amsterdame0831972019-03-12 11:07:31 -04001# Error Values: Frequently Asked Questions
2
3The Go 2 [error values proposal](https://go.googlesource.com/proposal/+/master/design/29934-error-values.md) adds functionality to the [`errors`](https://tip.golang.org/pkg/errors) and [`fmt`](https://tip.golang.org/pkg/fmt) packages of the standard library for Go 1.13. There is also a compatibility package, [`golang.org/x/xerrors`](https://godoc.org/golang.org/x/xerrors), for earlier Go versions.
4
5We suggest using the `xerrors` package for backwards compatibility. When you no longer wish to support Go versions before 1.13, use the corresponding standard library functions.
6
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -04007## How should I change my error-handling code to work with the new features?
Jonathan Amsterdame0831972019-03-12 11:07:31 -04008
9You need to be prepared that errors you get may be wrapped.
10
11- If you currently compare errors using `==`, use `xerrors.Is` instead. Example:
12 ```
13 if err == io.ErrUnexpectedEOF
14 ```
15 becomes
16 ```
17 if xerrors.Is(err, io.ErrUnexpectedEOF)
18 ```
19
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040020 - Checks of the form `if err != nil` need not be changed.
Jonathan Amsterdam6f8531f2019-03-12 11:42:34 -040021 - Comparisons to `io.EOF` need not be changed, because `io.EOF` should never be wrapped.
Jonathan Amsterdame0831972019-03-12 11:07:31 -040022
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040023- If you check for an error type using a type assertion or type switch, use `xerrors.As` instead. Example:
24 ```
25 if e, ok := err.(*os.PathError); ok
26 ```
27 becomes
28 ```
29 if e := &os.PathError{}; xerrors.As(err, &e)
30 ```
Jonathan Amsterdamab6ed5d2019-03-12 14:32:03 -040031 - Also use this pattern to check whether an error implements an interface. (This is one of those rare cases when a pointer to an interface is appropriate.)
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040032 - Rewrite a type switch as a sequence of if-elses.
Jonathan Amsterdam82dc7b62019-03-12 18:00:43 -040033
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040034## What formatting verb should I use to display errors?
35
36It depends who you expect to see the error message, and why.
37
38- If you are displaying an error so someone can diagnose a problem in your code, use `%+v` to display the maximum amount of detail. For example, an error that indicates a bug in your program should be displayed (or at least logged) with `%+v`.
39
40- If the person who sees the error message is unlikely to know or care about modifying your program, you probably want to use `%v` (or, equivalently for errors, `%s`). For example, an error reading a file from a command-line program should probably be displayed with `%v`.
41
42## I am already using `fmt.Errorf` with `%v` or `%s` to provide context for an error. When should I switch to `%w`?
43
44It's common to see code like
45```
46if err := frob(thing); err != nil {
47 return fmt.Errorf("while frobbing: %v", err)
48}
49```
50With the new error features, that code continues to work as before; the only difference is that more information is captured and displayed.
51
52Changing from `%v` to `%w` doesn't change how the error is displayed, but it does *wrap* `err`, the final argument to the `fmt.Errorf` call. Now the caller can access `err`, either by calling `Unwrap` on the returned error, or by using `[x]errors.Is` or `[x]errors.As` (which are merely convenience functions built on top of `Unwrap`).
53
54So use `%w` if you want to expose the underlying error to your callers. Keep in mind that doing so may be exposing implementation detail that can constrain the evolution of your code. Callers can depend on the type and value of the error you're wrapping, so changing that error can now break them. For example, if your `accessDatabase` function uses Go's `database/sql` package, then it may encounter a `sql.ErrTxDone` error. If you return that error with `fmt.Errorf("accessing DB: %v", err)` then callers won't see that `sql.ErrTxtDone` is part of the error you return. But if you instead return `fmt.Errorf("accessing DB: %w", err)`, then a caller could reasonably write
55```
56err := accessDatabase(...)
57if xerrors.Is(err, sql.ErrTxDone)
58```
59At that point, you must always return `sql.ErrTxDone` if you don't want to break your clients, even if you switch to a different database package.
60
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040061## How can I add context to an error I'm already returning without breaking clients?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040062
63Say your code now looks like
64```
65return err
66```
67and you decide that you want to add more information to `err` before you return it. If you write
68```
69return fmt.Errorf("more info: %v", err)
70```
71then you might break your clients, because the identity of `err` is lost; only its message remains.
72
73You could instead wrap the error by using `%w`, writing
74```
75return fmt.Errorf("more info: %w", err)
76```
77This will still break clients who use `==` or type assertion to test errors. But as we discussed in the first question of this FAQ, consumers of errors should migrate to the `Is` and `As` functions. If you can be sure that your clients have done so, then it is not a breaking change to switch from
78```
79return err
80```
81to
82```
83return fmt.Errorf("more info: %w", err)
84```
85
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040086## I'm writing new code, with no clients. Should I wrap returned errors or not?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040087
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040088Since you have no clients, you aren't constrained by backwards compatibility. But you still need to balance two opposing considerations:
89- Giving client code access to underlying errors can help it make decisions, which can lead to better software.
90- Every error you expose becomes part of your API: your clients may come to rely on it, so you can't change it.
Jonathan Amsterdam491a4422019-03-12 16:02:42 -040091
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040092For each error you return, you have to weigh the choice between helping your clients and locking yourself in. Of course, this choice is not unique to errors. In general, you have to decide if a feature of your code is important for clients to know, or an implementation detail.
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040093
Jonathan Amsterdam491a4422019-03-12 16:02:42 -040094With errors, though, there is an intermediate choice: you can expose error details to people reading your code's error messages, without exposing the errors themselves to client code. You do that by using `fmt.Errorf` with `%s` or `%v` (as in the past), or by implementing the [`errors.Formatter`](https://tip.golang.org/pkg/errors/#Formatter) interface.
Jonathan Amsterdame0831972019-03-12 11:07:31 -040095
Jonathan Amsterdam38b484a2019-03-12 17:55:04 -040096## I maintain a package that exports an error-checking predicate function. How should I adapt to the new features?
97
98Your package has a function or method `IsX(error) bool` that reports whether an error has some property. Your situation is like that of the standard `os` package, which has several such methods. We recommend the approach we took there. The `os` package has several predicates, but we treated them all the same. For concreteness, we'll look at `os.IsExist`.
99
100A natural thought would be to modify the predicate to unwrap the error it is passed, checking the property for each error in the chain of wrapped errors. We decided not to do this. A change in the behavior of `os.IsExist` for Go 1.13 would make it incompatible with earlier Go versions.
101
102Instead, we made `errors.Is(err, os.ErrExist)` behave like `os.IsExist`, except that `Is` unwraps. (We did this by having some internal error types implement an `Is` method, as described in the documentation for [`errors.Is`](https://tip.golang.org/pkg/errors/#Is).) Using `errors.Is` will always work correctly, because it only will exist in Go versions 1.13 and higher. For older version of Go, you should unwrap the error yourself before passing it to `os.IsExist`.