Jonathan Amsterdam
Russ Cox
Marcel van Lohuizen
Damien Neil
Last updated: January 25, 2019
Discussion at: https://golang.org/issue/29934
Past discussion at:
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
.
We provided background and a rationale in our draft designs for error inspection and printing. 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.
Others have pointed out that indiscriminate wrapping can expose implementation details, introducing undesired coupling between packages. As an example, the errgo
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.
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.
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 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
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 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).
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
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
.
os
packageThe 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
.
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, which will work with both Go 1.13 and earlier versions and will provide the following:
Wrapper
, Frame
, Formatter
and Printer
types described above.Unwrap
, Is
, As
, Opaque
and Caller
functions described above.New
function that is a drop-in replacement for errors.New
, but returns an error that behaves as described above.Errorf
function that is a drop-in replacement for fmt.Errorf
, except that it behaves as described above.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 { ... }
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 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.
None of the proposed changes violates the Go 1 compatibility guidelines. 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
.
The implementation requires changes to the standard library.
The 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!”, 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 package, also by Marcel, will provide code that can be used with earlier Go versions.