Jonathan Amsterdam | e083197 | 2019-03-12 11:07:31 -0400 | [diff] [blame] | 1 | # Error Values: Frequently Asked Questions |
| 2 | |
| 3 | The 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 | |
| 5 | We 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 Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 7 | ## How should I change my error-handling code to work with the new features? |
Jonathan Amsterdam | e083197 | 2019-03-12 11:07:31 -0400 | [diff] [blame] | 8 | |
| 9 | You 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 Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 20 | - Checks of the form `if err != nil` need not be changed. |
Jonathan Amsterdam | 6f8531f | 2019-03-12 11:42:34 -0400 | [diff] [blame] | 21 | - Comparisons to `io.EOF` need not be changed, because `io.EOF` should never be wrapped. |
Jonathan Amsterdam | e083197 | 2019-03-12 11:07:31 -0400 | [diff] [blame] | 22 | |
Jonathan Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 23 | - 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 | ``` |
Jonathan Amsterdam | 03688b6 | 2019-03-22 16:27:02 -0400 | [diff] [blame^] | 29 | var e *os.PathError |
Agniva De Sarker | 6ce7a0f | 2019-03-19 11:13:48 +0530 | [diff] [blame] | 30 | if xerrors.As(err, &e) |
Jonathan Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 31 | ``` |
Jonathan Amsterdam | ab6ed5d | 2019-03-12 14:32:03 -0400 | [diff] [blame] | 32 | - 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 Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 33 | - Rewrite a type switch as a sequence of if-elses. |
Jonathan Amsterdam | 82dc7b6 | 2019-03-12 18:00:43 -0400 | [diff] [blame] | 34 | |
Jonathan Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 35 | ## What formatting verb should I use to display errors? |
| 36 | |
| 37 | It depends who you expect to see the error message, and why. |
| 38 | |
| 39 | - 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`. |
| 40 | |
| 41 | - 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`. |
| 42 | |
| 43 | ## I am already using `fmt.Errorf` with `%v` or `%s` to provide context for an error. When should I switch to `%w`? |
| 44 | |
| 45 | It's common to see code like |
| 46 | ``` |
| 47 | if err := frob(thing); err != nil { |
| 48 | return fmt.Errorf("while frobbing: %v", err) |
| 49 | } |
| 50 | ``` |
| 51 | With the new error features, that code continues to work as before; the only difference is that more information is captured and displayed. |
| 52 | |
Jonathan Amsterdam | 634c24d | 2019-03-13 11:51:47 -0400 | [diff] [blame] | 53 | Changing 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 `errors.Is` or `errors.As` (which are merely convenience functions built on top of `Unwrap`). |
Jonathan Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 54 | |
| 55 | So 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 |
| 56 | ``` |
| 57 | err := accessDatabase(...) |
| 58 | if xerrors.Is(err, sql.ErrTxDone) |
| 59 | ``` |
| 60 | At 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. |
| 61 | |
Jonathan Amsterdam | 6eb18ff | 2019-03-12 14:21:26 -0400 | [diff] [blame] | 62 | ## How can I add context to an error I'm already returning without breaking clients? |
Jonathan Amsterdam | 744c933 | 2019-03-12 13:47:47 -0400 | [diff] [blame] | 63 | |
| 64 | Say your code now looks like |
| 65 | ``` |
| 66 | return err |
| 67 | ``` |
| 68 | and you decide that you want to add more information to `err` before you return it. If you write |
| 69 | ``` |
| 70 | return fmt.Errorf("more info: %v", err) |
| 71 | ``` |
| 72 | then you might break your clients, because the identity of `err` is lost; only its message remains. |
| 73 | |
| 74 | You could instead wrap the error by using `%w`, writing |
| 75 | ``` |
| 76 | return fmt.Errorf("more info: %w", err) |
| 77 | ``` |
| 78 | This 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 |
| 79 | ``` |
| 80 | return err |
| 81 | ``` |
| 82 | to |
| 83 | ``` |
| 84 | return fmt.Errorf("more info: %w", err) |
| 85 | ``` |
| 86 | |
Jonathan Amsterdam | 6eb18ff | 2019-03-12 14:21:26 -0400 | [diff] [blame] | 87 | ## I'm writing new code, with no clients. Should I wrap returned errors or not? |
Jonathan Amsterdam | 744c933 | 2019-03-12 13:47:47 -0400 | [diff] [blame] | 88 | |
Jonathan Amsterdam | 6eb18ff | 2019-03-12 14:21:26 -0400 | [diff] [blame] | 89 | Since you have no clients, you aren't constrained by backwards compatibility. But you still need to balance two opposing considerations: |
| 90 | - Giving client code access to underlying errors can help it make decisions, which can lead to better software. |
| 91 | - Every error you expose becomes part of your API: your clients may come to rely on it, so you can't change it. |
Jonathan Amsterdam | 491a442 | 2019-03-12 16:02:42 -0400 | [diff] [blame] | 92 | |
Jonathan Amsterdam | 6eb18ff | 2019-03-12 14:21:26 -0400 | [diff] [blame] | 93 | For 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 Amsterdam | 960c70b | 2019-03-12 13:18:21 -0400 | [diff] [blame] | 94 | |
Jonathan Amsterdam | 56202d7 | 2019-03-12 18:03:56 -0400 | [diff] [blame] | 95 | With 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 on a custom error type. |
Jonathan Amsterdam | e083197 | 2019-03-12 11:07:31 -0400 | [diff] [blame] | 96 | |
Jonathan Amsterdam | 38b484a | 2019-03-12 17:55:04 -0400 | [diff] [blame] | 97 | ## I maintain a package that exports an error-checking predicate function. How should I adapt to the new features? |
| 98 | |
Jonathan Amsterdam | 634c24d | 2019-03-13 11:51:47 -0400 | [diff] [blame] | 99 | Your 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 functions. 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`. |
Jonathan Amsterdam | 38b484a | 2019-03-12 17:55:04 -0400 | [diff] [blame] | 100 | |
| 101 | A 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. |
| 102 | |
Jonathan Amsterdam | 7c8842f | 2019-03-18 06:37:21 -0400 | [diff] [blame] | 103 | Instead, 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 recursively unwrap the error yourself, calling `os.IsExist` on each underlying error. |
| 104 | |
| 105 | So we recommend: |
| 106 | - Don't change your `IsX(error) bool` function. |
| 107 | - If you don't already have one, add a global variable whose type implements `error` that represents the |
| 108 | condition that your function tests: |
| 109 | ``` |
| 110 | var ErrX = errors.New("has property X") |
| 111 | ``` |
| 112 | - Add an `Is` method to the types for which `IsX` returns true. The `Is` method should return true if its argument |
Jonathan Amsterdam | 3cc70a1 | 2019-03-20 19:18:56 -0400 | [diff] [blame] | 113 | equals `ErrX`. |
| 114 | |
| 115 | ## I have a type that implements `error` and holds a nested error. How should I adapt it to the new features? |
| 116 | |
| 117 | If your type already exposes the error, write an `Unwrap` method. |
| 118 | |
| 119 | For example, perhaps your type looks like |
| 120 | ``` |
| 121 | type MyError struct { |
| 122 | Err error |
| 123 | // other fields |
| 124 | } |
| 125 | |
| 126 | func (e *MyError) Error() string { return ... } |
| 127 | ``` |
| 128 | |
| 129 | Then you should add |
| 130 | ``` |
| 131 | func (e *MyError) Unwrap() error { return e.Err } |
| 132 | ``` |
| 133 | |
| 134 | Your type will then work correctly with the `Is` and `As` functions of `errors` and `xerrors`. |
| 135 | |
| 136 | We've done that for [`os.PathError`](https://tip.golang.org/pkg/os/#PathError.Unwrap) and other, similar types in the standard library. |
| 137 | |
| 138 | It's clear that writing an `Unwrap` method is the right choice if the nested error is exported, or otherwise visible to code outside your package, such as via a method like `Unwrap`. But if the nested error is not exposed to outside code, you probably should keep it that way. Making the error visible by returning it from `Unwrap` will enable your clients to depend on the type of the nested error, which can expose implementation details and constrain the evolution of your package. See the discussion of `%w` above for more. |