design: add error values proposal After approval, I'll create an issue and change the name of this file to match. Change-Id: I710fbad3f37ef03ab256c1422007b63b664e9e06
diff --git a/design/XXXXX-error-values.md b/design/XXXXX-error-values.md new file mode 100644 index 0000000..3ea5160 --- /dev/null +++ b/design/XXXXX-error-values.md
@@ -0,0 +1,313 @@ +# Proposal: Go 2 Error Inspection + +Jonathan Amsterdam\ +Russ Cox\ +Marcel van Lohuizen\ +Damien Neil + +Last updated: January 24, 2019 + +Discussion at: TODO NEW ISSUE + +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 and returns true. An error +// matches a type if it is of the same type, or if it has a method As(interface{}) bool +// such that As(target) returns true. As will panic if target is nil or not a pointer. +// +// 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 +``` + +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 + /path/to/database.go:111 + - call myserver.Method: + /path/to/grpc.go:222 + - dial myserver:3333: + /path/to/net/dial.go:333 + - open /etc/resolv.conf: + /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`. + +### 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) + } + ``` + +## 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.