Updated ErrorValueFAQ (markdown)

Jonathan Amsterdam 2019-06-30 20:32:03 -04:00
parent 9a3ad05094
commit 9a6fa039c2

@ -2,32 +2,32 @@
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.
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.
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. This FAQ uses the `errors` and `fmt` packages from Go 1.13.
## How should I change my error-handling code to work with the new features?
You need to be prepared that errors you get may be wrapped.
- If you currently compare errors using `==`, use `xerrors.Is` instead. Example:
- If you currently compare errors using `==`, use `errors.Is` instead. Example:
```
if err == io.ErrUnexpectedEOF
```
becomes
```
if xerrors.Is(err, io.ErrUnexpectedEOF)
if errors.Is(err, io.ErrUnexpectedEOF)
```
- Checks of the form `if err != nil` need not be changed.
- Comparisons to `io.EOF` need not be changed, because `io.EOF` should never be wrapped.
- If you check for an error type using a type assertion or type switch, use `xerrors.As` instead. Example:
- If you check for an error type using a type assertion or type switch, use `errors.As` instead. Example:
```
if e, ok := err.(*os.PathError); ok
```
becomes
```
var e *os.PathError
if xerrors.As(err, &e)
if errors.As(err, &e)
```
- 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.)
- Rewrite a type switch as a sequence of if-elses.
@ -40,14 +40,12 @@ if err := frob(thing); err != nil {
return fmt.Errorf("while frobbing: %v", err)
}
```
With the new error features, that code continues to work as before; the only difference is that more information is captured and displayed.
With 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`.
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`).
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
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 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
```
err := accessDatabase(...)
if xerrors.Is(err, sql.ErrTxDone)
err := pkg.AccessDatabase(...)
if errors.Is(err, sql.ErrTxDone) ...
```
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.
@ -58,6 +56,7 @@ Say your code now looks like
return err
```
and you decide that you want to add more information to `err` before you return it. If you write
```
return fmt.Errorf("more info: %v", err)
```
@ -67,7 +66,7 @@ You could instead wrap the error by using `%w`, writing
```
return fmt.Errorf("more info: %w", err)
```
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
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 `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
```
return err
```
@ -82,9 +81,9 @@ Since you have no clients, you aren't constrained by backwards compatibility. Bu
- Giving client code access to underlying errors can help it make decisions, which can lead to better software.
- Every error you expose becomes part of your API: your clients may come to rely on it, so you can't change it.
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.
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; 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.
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.
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. 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.
## I maintain a package that exports an error-checking predicate function. How should I adapt to the new features?