| # Proposal: Go 2 Error Inspection |
| |
| Jonathan Amsterdam\ |
| Russ Cox\ |
| Marcel van Lohuizen\ |
| Damien Neil |
| |
| Last updated: January 25, 2019 |
| |
| Discussion at: https://golang.org/issue/29934 |
| |
| Past discussion at: |
| - https://golang.org/design/go2draft-error-inspection |
| - https://golang.org/design/go2draft-error-printing |
| - https://golang.org/wiki/Go2ErrorValuesFeedback |
| |
| ## Abstract |
| |
| We propose several additions and changes to the standard library’s `errors` and |
| `fmt` packages, with the goal of making errors more informative for both |
| programs and people. We codify the common practice of wrapping one error in |
| another, and provide two convenience functions, `Is` and `As`, for traversing |
| the chain of wrapped errors. |
| |
| We enrich error formatting by making it easy for error types to display |
| additional information when detailed output is requested with the `%+v` |
| formatting directive. |
| |
| We add function, file and line information to the errors returned by |
| `errors.New` and `fmt.Errorf`, and provide a `Frame` type to simplify adding |
| location information to any error type. |
| |
| We add support for detail formatting and wrapping to `fmt.Errorf`. |
| |
| ## Background |
| |
| We provided background and a rationale in our [draft designs for error |
| inspection](https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md) |
| and |
| [printing](https://go.googlesource.com/proposal/+/master/design/go2draft-error-printing.md). |
| Here we provide a brief summary. |
| |
| While Go 1’s definition of errors is open-ended, its actual support for errors |
| is minimal, providing only string messages. Many Go programmers want to provide |
| additional information with errors, and of course nothing has stopped them from |
| doing so. But one pattern has become so pervasive that we feel it is worth |
| enshrining in the standard library: the idea of wrapping one error in another |
| that provides additional information. Several packages provide wrapping support, |
| including the popular |
| [github.com/pkg/errors](https://godoc.org/github.com/pkg/errors). |
| |
| Others have pointed out that indiscriminate wrapping can expose implementation |
| details, introducing undesired coupling between packages. As an example, the |
| [`errgo`](https://godoc.org/gopkg.in/errgo.v2) package lets users control |
| wrapping to hide details. |
| |
| Another popular request is for location information in the form of stack frames. |
| Some advocate for complete stack traces, while others prefer to add location |
| information only at certain points. |
| |
| ## Proposal |
| |
| We add a standard way to wrap errors to the standard library, to encourage the |
| practice and to make it easy to use. We separate error wrapping, designed for |
| programs, from error formatting, designed for people. This makes it possible to |
| hide implementation details from programs while displaying them for diagnosis. |
| We also add location (stack frame) information to standard errors and make it |
| easy for developers to include location information in their own errors. |
| |
| All of the API changes are in the `errors` package. We also change the behavior |
| of parts of the `fmt` package. |
| |
| ### Wrapping |
| |
| An error that wraps another error should implement `Wrapper` by defining an `Unwrap` method. |
| ``` |
| type Wrapper interface { |
| // Unwrap returns the next error in the error chain. |
| // If there is no next error, Unwrap returns nil. |
| Unwrap() error |
| } |
| ``` |
| The `Unwrap` function is a convenience for calling the `Unwrap` method if one exists. |
| ``` |
| // Unwrap returns the result of calling the Unwrap method on err, if err implements Unwrap. |
| // Otherwise, Unwrap returns nil. |
| func Unwrap(err error) error |
| ``` |
| |
| The `Is` function follows the chain of errors by calling `Unwrap`, searching for |
| one that matches a target. It is intended to be used instead of equality for |
| matching sentinel errors (unique error values). An error type can implement an |
| `Is` method to override the default behavior. |
| |
| ``` |
| // Is reports whether any error in err's chain matches target. |
| // |
| // An error is considered to match a target if it is equal to that target or if |
| // it implements a method Is(error) bool such that Is(target) returns true. |
| func Is(err, target error) bool |
| ``` |
| |
| The `As` function searches the wrapping chain for an error whose type matches |
| that of a target. An error type can implement `As` to override the default |
| behavior. |
| ``` |
| // As finds the first error in err's chain that matches the type to which target |
| // points, and if so, sets the target to its value and returns true. An error |
| // matches a type if it is assignable to the target type, or if it has a method |
| // As(interface{}) bool such that As(target) returns true. As will panic if target |
| // is not a non-nil pointer to a type which implements error or is of interface type. |
| // |
| // The As method should set the target to its value and return true if err |
| // matches the type to which target points. |
| func As(err error, target interface{}) bool |
| ``` |
| |
| A [vet check](https://golang.org/cmd/vet) will be implemented to check that |
| the `target` argument is valid. |
| |
| |
| The `Opaque` function hides a wrapped error from programmatic inspection. |
| ``` |
| // Opaque returns an error with the same error formatting as err |
| // but that does not match err and cannot be unwrapped. |
| func Opaque(err error) error |
| ``` |
| |
| ### Stack Frames |
| |
| The `Frame` type holds location information: the function name, file and line of |
| a single stack frame. |
| ``` |
| type Frame struct { |
| // unexported fields |
| } |
| ``` |
| |
| The `Caller` function returns a `Frame` at a given distance from the call site. |
| It is a convenience wrapper around `runtime.Callers`. |
| ``` |
| func Caller(skip int) Frame |
| ``` |
| |
| To display itself, `Frame` implements a `Format` method that takes a `Printer`. |
| See [Formatting](#formatting) below for the definition of `Printer`. |
| ``` |
| // Format prints the stack as error detail. |
| // It should be called from an error's FormatError implementation, |
| // before printing any other error detail. |
| func (f Frame) Format(p Printer) |
| ``` |
| |
| The errors returned from `errors.New` and `fmt.Errorf` include a `Frame` which |
| will be displayed when the error is formatted with additional detail (see |
| below). |
| |
| ### Formatting |
| |
| We introduce two interfaces for error formatting into the `errors` package and |
| change the behavior of formatted output (the `Print`, `Println` and `Printf` |
| functions of the `fmt` package and their `S` and `F` variants) to recognize |
| them. |
| |
| The `errors.Formatter` interface adds the `FormatError` method to the `error` interface. |
| ``` |
| type Formatter interface { |
| error |
| |
| // FormatError prints the receiver's first error and returns the next error to |
| // be formatted, if any. |
| FormatError(p Printer) (next error) |
| } |
| ``` |
| An error type that wants to control its formatted output should implement |
| `Formatter`. During formatted output, `FormatError` will be called if it is |
| implemented, in preference to both the `Error` and `Format` methods. |
| |
| `FormatError` returns an error, which will also be output if it is not `nil`. If |
| an error type implements `Wrapper`, then it would likely return the result of |
| `Unwrap` from `FormatError`, but it is not required to do so. An error that does |
| not implement `Wrapper` may still return a non-nil value from `FormatError`, |
| hiding implementation detail from programs while still displaying it to users. |
| |
| The `Printer` passed to `FormatError` provides `Print` and `Printf` methods to |
| generate output, as well as a `Detail` method that reports whether the printing |
| is happening in "detail mode" (triggered by `%+v`). Implementations should first |
| call `Printer.Detail`, and if it returns true should then print detailed |
| information like the location of the error. |
| ``` |
| type Printer interface { |
| // Print appends args to the message output. |
| Print(args ...interface{}) |
| |
| // Printf writes a formatted string. |
| Printf(format string, args ...interface{}) |
| |
| // Detail reports whether error detail is requested. |
| // After the first call to Detail, all text written to the Printer |
| // is formatted as additional detail, or ignored when |
| // detail has not been requested. |
| // If Detail returns false, the caller can avoid printing the detail at all. |
| Detail() bool |
| } |
| ``` |
| |
| When not in detail mode (`%v`, or in `Print` and `Println` functions and their |
| variants), errors print on a single line. In detail mode, errors print over |
| multiple lines, as shown here: |
| ``` |
| write users database: |
| more detail here |
| mypkg/db.Open |
| /path/to/database.go:111 |
| - call myserver.Method: |
| google.golang.org/grpc.Invoke |
| /path/to/grpc.go:222 |
| - dial myserver:3333: |
| net.Dial |
| /path/to/net/dial.go:333 |
| - open /etc/resolv.conf: |
| os.Open |
| /path/to/os/open.go:444 |
| - permission denied |
| ``` |
| |
| ### Changes to `fmt.Errorf` |
| |
| We modify the behavior of `fmt.Errorf` in the following case: if the last |
| argument is an error `err` and the format string ends with `: %s`, `: %v`, or |
| `: %w`, then the returned error will implement `FormatError` to return `err`. In |
| the case of the new verb `%w`, the returned error will also implement |
| `errors.Wrapper` with an `Unwrap` method returning `err`. |
| |
| ### Changes to the `os` package |
| |
| The `os` package contains a several predicate functions which test |
| an error against a condition: `IsExist`, `IsNotExist`, `IsPermission`, |
| and `IsTimeout`. For each of these conditions, we modify the `os` |
| package so that `errors.Is(err, os.ErrX)` returns true when |
| `os.IsX(err)` is true for any error in `err`'s chain. The `os` package |
| already contains `ErrExist`, `ErrIsNotExist`, and `ErrPermission` |
| sentinel values; we will add `ErrTimeout`. |
| |
| ### Transition |
| |
| If we add this functionality to the standard library in Go 1.13, code that needs |
| to keep building with previous versions of Go will not be able to depend on the |
| new standard library. While every such package could use build tags and multiple |
| source files, that seems like too much work for a smooth transition. |
| |
| To help the transition, we will publish a new package |
| [golang.org/x/xerrors](https://godoc.org/golang.org/x/xerrors), which will work |
| with both Go 1.13 and earlier versions and will provide the following: |
| |
| - The `Wrapper`, `Frame`, `Formatter` and `Printer` types described above. |
| - The `Unwrap`, `Is`, `As`, `Opaque` and `Caller` functions described above. |
| - A `New` function that is a drop-in replacement for `errors.New`, but returns |
| an error that behaves as described above. |
| - An `Errorf` function that is a drop-in replacement for `fmt.Errorf`, except |
| that it behaves as described above. |
| - A `FormatError` function that adapts the `Format` method to use the new |
| formatting implementation. An error implementation can make sure earlier Go |
| versions call its `FormatError` method by adding this `Format` method: |
| ``` |
| type MyError ... |
| |
| func (m *MyError) Format(f fmt.State, c rune) { // implements fmt.Formatter |
| xerrors.FormatError(m, f, c) // will call m.FormatError |
| } |
| |
| func (m *MyError) Error() string { ... } |
| func (m *MyError) FormatError(p xerrors.Printer) error { ... } |
| ``` |
| |
| ## Rationale |
| |
| We provided a rationale for most of these changes in the draft designs (linked |
| above). Here we justify the parts of the design that have changed or been added |
| since those documents were written. |
| |
| - The original draft design proposed that the `As` function use generics, and |
| suggested an `AsValue` function as a temporary alternative until generics were |
| available. We find that the `As` function in the form we describe here is just |
| as concise and readable as a generic version would be, if not more so. |
| |
| - We added the ability for error types to modify the default behavior of the |
| `Is` and `As` functions by implementing `Is` and `As` methods, respectively. |
| We felt that the extra power these give to error implementers was worth the |
| slight additional complexity. |
| |
| - We included a `Frame` in the errors returned by `errors.New` and `fmt.Errorf` |
| so that existing Go programs could reap the benefits of location information. |
| We benchmarked the slowdown from fetching stack information and felt that it |
| was tolerable. |
| |
| - We changed the behavior of `fmt.Errorf` for the same reason: so existing Go |
| programs could enjoy the new formatting behavior without modification. We |
| decided against wrapping errors passed to `fmt.Errorf` by default, since doing |
| so would effectively change the exposed surface of a package by revealing the |
| types of the wrapped errors. Instead, we require that programmers opt in to |
| wrapping by using the new formatting verb `%w`. |
| |
| - Lastly, we want to acknowledge the several comments on the [feedback |
| wiki](https://golang.org/wiki/Go2ErrorValuesFeedback) that suggested that we |
| go further by incorporating a way to represent multiple errors as a single |
| error value. We understand that this is a popular request, but at this point |
| we feel we have introduced enough new features for one proposal, and we’d like |
| to see how these work out before adding more. We can always add a multi-error |
| type in a later proposal, and meanwhile it remains easy to write your own. |
| |
| ## Compatibility |
| |
| None of the proposed changes violates the [Go 1 compatibility |
| guidelines](https://golang.org/doc/go1compat). Gathering frame information may |
| slow down `errors.New` slightly, but this is unlikely to affect practical |
| programs. Errors constructed with `errors.New` and `fmt.Errorf` will display |
| differently with `%+v`. |
| |
| ## Implementation |
| |
| The implementation requires changes to the standard library. |
| |
| The [golang.org/x/exp/errors](https://godoc.org/golang.org/x/exp/errors) package |
| contains a proposed implementation by Marcel van Lohuizen. We intend to make the |
| changes to the main tree at the start of the Go 1.13 cycle, around February 1. |
| |
| As noted in our blog post ["Go 2, here we |
| come!"](https://blog.golang.org/go2-here-we-come), the development cycle will |
| serve as a way to collect experience about these new features and feedback from |
| (very) early adopters. |
| |
| As noted above, the |
| [golang.org/x/xerrors](https://godoc.org/golang.org/x/xerrors) package, also by |
| Marcel, will provide code that can be used with earlier Go versions. |