blob: 9fc4e9745e24c342403879e8e38a527aef6e7903 [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 ```
Jonathan Amsterdam03688b62019-03-22 16:27:02 -040029 var e *os.PathError
Agniva De Sarker6ce7a0f2019-03-19 11:13:48 +053030 if xerrors.As(err, &e)
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040031 ```
Jonathan Amsterdamab6ed5d2019-03-12 14:32:03 -040032 - 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 -040033 - Rewrite a type switch as a sequence of if-elses.
Jonathan Amsterdam82dc7b62019-03-12 18:00:43 -040034
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040035## What formatting verb should I use to display errors?
36
37It 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
45It's common to see code like
46```
47if err := frob(thing); err != nil {
48 return fmt.Errorf("while frobbing: %v", err)
49}
50```
51With the new error features, that code continues to work as before; the only difference is that more information is captured and displayed.
52
Jonathan Amsterdam634c24d2019-03-13 11:51:47 -040053Changing 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 Amsterdam960c70b2019-03-12 13:18:21 -040054
55So 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```
57err := accessDatabase(...)
58if xerrors.Is(err, sql.ErrTxDone)
59```
60At 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 Amsterdam6eb18ff2019-03-12 14:21:26 -040062## How can I add context to an error I'm already returning without breaking clients?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040063
64Say your code now looks like
65```
66return err
67```
68and you decide that you want to add more information to `err` before you return it. If you write
69```
70return fmt.Errorf("more info: %v", err)
71```
72then you might break your clients, because the identity of `err` is lost; only its message remains.
73
74You could instead wrap the error by using `%w`, writing
75```
76return fmt.Errorf("more info: %w", err)
77```
78This 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```
80return err
81```
82to
83```
84return fmt.Errorf("more info: %w", err)
85```
86
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040087## I'm writing new code, with no clients. Should I wrap returned errors or not?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040088
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040089Since 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 Amsterdam491a4422019-03-12 16:02:42 -040092
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040093For 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 -040094
Jonathan Amsterdam56202d72019-03-12 18:03:56 -040095With 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 Amsterdame0831972019-03-12 11:07:31 -040096
Jonathan Amsterdam38b484a2019-03-12 17:55:04 -040097## I maintain a package that exports an error-checking predicate function. How should I adapt to the new features?
98
Jonathan Amsterdam634c24d2019-03-13 11:51:47 -040099Your 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 Amsterdam38b484a2019-03-12 17:55:04 -0400100
101A 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 Amsterdam7c8842f2019-03-18 06:37:21 -0400103Instead, 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
105So 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 Amsterdam3cc70a12019-03-20 19:18:56 -0400113 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
117If your type already exposes the error, write an `Unwrap` method.
118
119For example, perhaps your type looks like
120```
121type MyError struct {
122 Err error
123 // other fields
124}
125
126func (e *MyError) Error() string { return ... }
127```
128
129Then you should add
130```
131func (e *MyError) Unwrap() error { return e.Err }
132```
133
134Your type will then work correctly with the `Is` and `As` functions of `errors` and `xerrors`.
135
136We've done that for [`os.PathError`](https://tip.golang.org/pkg/os/#PathError.Unwrap) and other, similar types in the standard library.
137
138It'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.