blob: 1e353e982e61e9724ccd45c1f4b9ba3f25d5e7fa [file] [log] [blame] [view]
Jonathan Amsterdame0831972019-03-12 11:07:31 -04001# Error Values: Frequently Asked Questions
2
Sean Liao6fe9f522022-01-22 16:52:18 +01003The 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://pkg.go.dev/golang.org/x/xerrors), for earlier Go versions.
Jonathan Amsterdame0831972019-03-12 11:07:31 -04004
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -04005We 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. This FAQ uses the `errors` and `fmt` packages from Go 1.13.
Jonathan Amsterdame0831972019-03-12 11:07:31 -04006
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
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040011- If you currently compare errors using `==`, use `errors.Is` instead. Example:
Jonathan Amsterdame0831972019-03-12 11:07:31 -040012 ```
13 if err == io.ErrUnexpectedEOF
14 ```
15 becomes
16 ```
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040017 if errors.Is(err, io.ErrUnexpectedEOF)
Jonathan Amsterdame0831972019-03-12 11:07:31 -040018 ```
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 Amsterdam9a6fa032019-06-30 20:32:03 -040023- If you check for an error type using a type assertion or type switch, use `errors.As` instead. Example:
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040024 ```
25 if e, ok := err.(*os.PathError); ok
26 ```
27 becomes
28 ```
Jonathan Amsterdam03688b62019-03-22 16:27:02 -040029 var e *os.PathError
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040030 if errors.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## I am already using `fmt.Errorf` with `%v` or `%s` to provide context for an error. When should I switch to `%w`?
36
37It's common to see code like
38```
39if err := frob(thing); err != nil {
40 return fmt.Errorf("while frobbing: %v", err)
41}
42```
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040043With the new error features, that code continues to work exactly as before, constructing a string that includes the text of `err`. Changing from `%v` to `%w` doesn't change that string, but it does wrap `err`, allowing the caller to access it using `errors.Unwrap`, `errors.Is` or `errors.As`.
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040044
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040045So 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 the `AccessDatabase` function of your package `pkg` 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
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040046```
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040047err := pkg.AccessDatabase(...)
48if errors.Is(err, sql.ErrTxDone) ...
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040049```
50At 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.
51
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040052## How can I add context to an error I'm already returning without breaking clients?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040053
54Say your code now looks like
55```
56return err
57```
58and you decide that you want to add more information to `err` before you return it. If you write
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040059
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040060```
61return fmt.Errorf("more info: %v", err)
62```
63then you might break your clients, because the identity of `err` is lost; only its message remains.
64
65You could instead wrap the error by using `%w`, writing
66```
67return fmt.Errorf("more info: %w", err)
68```
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040069This 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 `errors.Is` and `errors.As` functions. If you can be sure that your clients have done so, then it is not a breaking change to switch from
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040070```
71return err
72```
73to
74```
75return fmt.Errorf("more info: %w", err)
76```
77
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040078## I'm writing new code, with no clients. Should I wrap returned errors or not?
Jonathan Amsterdam744c9332019-03-12 13:47:47 -040079
Jonathan Amsterdam6eb18ff2019-03-12 14:21:26 -040080Since you have no clients, you aren't constrained by backwards compatibility. But you still need to balance two opposing considerations:
81- Giving client code access to underlying errors can help it make decisions, which can lead to better software.
82- 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 -040083
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040084For 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; as a package author, you make many decisions about whether a feature of your code is important for clients to know or an implementation detail.
Jonathan Amsterdam960c70b2019-03-12 13:18:21 -040085
Jonathan Amsterdam9a6fa032019-06-30 20:32:03 -040086With 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. One way to do that is to put the details in a string using `fmt.Errorf` with `%s` or `%v`. Another is to write a custom error type, add the details to the string returned by its `Error` method, and avoid defining an `Unwrap` method.
Jonathan Amsterdame0831972019-03-12 11:07:31 -040087
Jonathan Amsterdam38b484a2019-03-12 17:55:04 -040088## I maintain a package that exports an error-checking predicate function. How should I adapt to the new features?
89
Jonathan Amsterdam7ec15a02019-09-04 05:57:03 -040090Your package has a function or method `IsX(error) bool` that reports whether an error has some property.
91A natural thought would be to modify `IsX` to unwrap the error it is passed, checking the property for each error in the chain of wrapped errors. We advise against doing this: the change in behavior could break your users.
Jonathan Amsterdam38b484a2019-03-12 17:55:04 -040092
Jonathan Amsterdam7ec15a02019-09-04 05:57:03 -040093Your 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 most of them the same. For concreteness, we'll look at `os.IsExist`.
Jonathan Amsterdam38b484a2019-03-12 17:55:04 -040094
Sean Liao3c9c9e12022-01-22 17:27:16 +010095Instead of changing `os.IsExist`, we made `errors.Is(err, os.ErrExist)` behave like it, except that `Is` unwraps. (We did this by having `syscall.Errno` implement an `Is` method, as described in the documentation for [`errors.Is`](https://pkg.go.dev/errors/#Is).) Using `errors.Is` will always work correctly, because it will exist only in Go versions 1.13 and higher. For older versions of Go, you should recursively unwrap the error yourself, calling `os.IsExist` on each underlying error.
Jonathan Amsterdam7c8842f2019-03-18 06:37:21 -040096
Jonathan Amsterdamf36c1082019-08-06 08:05:58 -040097This technique only works if you have control of the errors being wrapped, so you can add `Is` methods to them.
Jonathan Amsterdam65afc5f2019-08-06 08:24:54 -040098In that case, we recommend:
Jonathan Amsterdam7ec15a02019-09-04 05:57:03 -040099- Don't change your `IsX(error) bool` function; do change its documentation to clarify that it does not unwrap.
Jonathan Amsterdam7c8842f2019-03-18 06:37:21 -0400100- If you don't already have one, add a global variable whose type implements `error` that represents the
101 condition that your function tests:
102 ```
103 var ErrX = errors.New("has property X")
104 ```
105- 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 -0400106 equals `ErrX`.
107
Jonathan Amsterdam7ec15a02019-09-04 05:57:03 -0400108If you don't have control of all the errors that can have property X, you should instead consider adding another function that tests for the property while unwrapping, perhaps
Jonathan Amsterdamf36c1082019-08-06 08:05:58 -0400109```
Jonathan Amsterdam65afc5f2019-08-06 08:24:54 -0400110func IsXUnwrap(err error) bool {
111 for e := err; e != nil; e = errors.Unwrap(e) {
112 if IsX(e) {
113 return true
114 }
Jonathan Amsterdamf36c1082019-08-06 08:05:58 -0400115 }
Jonathan Amsterdam65afc5f2019-08-06 08:24:54 -0400116 return false
Jonathan Amsterdamf36c1082019-08-06 08:05:58 -0400117}
Jonathan Amsterdamf36c1082019-08-06 08:05:58 -0400118```
119
Jonathan Amsterdam7ec15a02019-09-04 05:57:03 -0400120Or you could leave things as they are, and let your users do the unwrapping themselves. Either way, you should still change the documentation of `IsX` to clarify that it does not unwrap.
Jonathan Amsterdam65afc5f2019-08-06 08:24:54 -0400121
Jonathan Amsterdam3cc70a12019-03-20 19:18:56 -0400122## I have a type that implements `error` and holds a nested error. How should I adapt it to the new features?
123
124If your type already exposes the error, write an `Unwrap` method.
125
126For example, perhaps your type looks like
127```
128type MyError struct {
129 Err error
130 // other fields
131}
132
133func (e *MyError) Error() string { return ... }
134```
135
136Then you should add
137```
138func (e *MyError) Unwrap() error { return e.Err }
139```
140
141Your type will then work correctly with the `Is` and `As` functions of `errors` and `xerrors`.
142
143We've done that for [`os.PathError`](https://tip.golang.org/pkg/os/#PathError.Unwrap) and other, similar types in the standard library.
144
145It'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.