For the most recent version of this proposal, see: #53435 (comment) below.
This is a variation on the rejected proposal #47811 (and perhaps that proposal should just be reopened), and an expansion on a comment in it.
Background
Since Go 1.13, an error may wrap another by providing an Unwrap method returning the wrapped error. The errors.Is and errors.As functions operate on chains of wrapped errors.
A common request is for a way to combine a list of errors into a single error.
Proposal
An error wraps multiple errors if its type has the method
Reusing the name Unwrap avoids ambiguity with the existing singular Unwrap method. Returning a 0-length list from Unwrap means the error doesn't wrap anything. Callers must not modify the list returned by Unwrap. The list returned by Unwrap must not contain any nil errors.
We replace the term "error chain" with "error tree".
The errors.Is and errors.As functions are updated to unwrap multiple errors. Is reports a match if any error in the tree matches. As finds the first matching error in a preorder traversal of the tree.
The errors.Join function provides a simple implementation of a multierr. It does not flatten errors.
// Join returns an error that wraps the given errors.
// Any nil error values are discarded.
// The error formats as the text of the given errors, separated by sep.
// Join returns nil if errs contains no non-nil values.
func Join(sep string, errs ...error) error
The fmt.Errorf function permits multiple instances of the %w formatting verb.
The errors.Split function retrieves the original errors from a combined error.
// Split returns the result of calling the Unwrap method on err,
// if err's type contains an Unwrap method returning []error.
// Otherwise, Split returns nil.
func Split(err error) []error
The errors.Unwrap function is unaffected: It returns nil when called on an error with an Unwrap() []error method.
Questions
Prior proposals have been declined on the grounds that this functionality can be implemented outside the standard library, and there was no good single answer to several important questions.
Why should this be in the standard library?
This proposal adds something which cannot be provided outside the standard library: Direct support for error trees in errors.Is and errors.As. Existing combining errors operate by providing Is and As methods which inspect the contained errors, requiring each implementation to duplicate this logic, possibly in incompatible ways. This is best handled in errors.Is and errors.As, for the same reason those functions handle singular unwrapping.
In addition, this proposal provides a common method for the ecosystem to use to represent combined errors, permitting interoperation between third-party implementations.
How are multiple errors formatted?
A principle of the errors package is that error formatting is up to the user. This proposal upholds that principle: The errors.Join function combines error text with a user-provided separator, and fmt.Errorf wraps multiple errors in a user-defined layout. If users have other formatting requirements, they can still create their own error implementations.
How do Is and As interact with combined errors?
Every major multierror package that I looked at (see "Prior art" below) implements the same behavior for Is and As: Is reports true if any error in the combined error matches, and As returns the first matching error. This proposal follows common practice.
Does creating a combined error flatten combined errors in the input?
The errors.Join function does not flatten errors. This is simple and comprehensible. Third-party packages can easily provide flattening if desired.
Should Split unwrap errors that wrap a single error?
The errors.Split function could call the single-wrapping Unwrap() error method when present, converting a non-nil result into a single-element slice. This would allow traversing an error tree with only calls to Split.
This might allow for a small improvement in the convenience of code which manually traverses an error tree, but it is rare for programs to manually traverse error chains today. Keeping Split as the inverse of Join is simpler.
Why does the name of the Split function not match the Unwrap method it calls?
Giving the single- and multiple-error wrapping methods the same name neatly avoids any questions of how to handle errors that implement both.
Split is a natural name for the function that undoes a Join.
While we could call the method Split, or the function UnwrapMultiple, or some variation on these options, the benefits of the above points outweigh the value in aligning the method name with the function name.
Prior art
There have been several previous proposals to add some form of combining error, including:
https://go.dev/issue/47811: add Errors as a standard way to represent multiple errors as a single error
https://go.dev/issue/48831: add NewJoinedErrors
https://go.dev/issue/20984: composite errors
https://go.dev/issue/52607: add With(err, other error) error
fmt.Errorf("%w: %w", err1, err2) is largely equivalent to With(err1, err2).
Credit to @jimmyfrasche for suggesting the method name Unwrap.
There are many implementations of combining errors in the world, including:
https://pkg.go.dev/github.com/hashicorp/go-multierror (8720 imports)
https://pkg.go.dev/go.uber.org/multierr (1513 imports)
https://pkg.go.dev/tailscale.com/util/multierr (2 imports)
For the most recent version of this proposal, see: #53435 (comment) below.
This is a variation on the rejected proposal #47811 (and perhaps that proposal should just be reopened), and an expansion on a comment in it.
Background
Since Go 1.13, an error may wrap another by providing an
Unwrapmethod returning the wrapped error. Theerrors.Isanderrors.Asfunctions operate on chains of wrapped errors.A common request is for a way to combine a list of errors into a single error.
Proposal
An error wraps multiple errors if its type has the method
Reusing the name
Unwrapavoids ambiguity with the existing singular Unwrap method. Returning a 0-length list fromUnwrapmeans the error doesn't wrap anything. Callers must not modify the list returned byUnwrap. The list returned byUnwrapmust not contain anynilerrors.We replace the term "error chain" with "error tree".
The
errors.Isanderrors.Asfunctions are updated to unwrap multiple errors.Isreports a match if any error in the tree matches.Asfinds the first matching error in a preorder traversal of the tree.The
errors.Joinfunction provides a simple implementation of a multierr. It does not flatten errors.The
fmt.Errorffunction permits multiple instances of the%wformatting verb.The
errors.Splitfunction retrieves the original errors from a combined error.The
errors.Unwrapfunction is unaffected: It returnsnilwhen called on an error with anUnwrap() []errormethod.Questions
Prior proposals have been declined on the grounds that this functionality can be implemented outside the standard library, and there was no good single answer to several important questions.
Why should this be in the standard library?
This proposal adds something which cannot be provided outside the standard library: Direct support for error trees in
errors.Isanderrors.As. Existing combining errors operate by providingIsandAsmethods which inspect the contained errors, requiring each implementation to duplicate this logic, possibly in incompatible ways. This is best handled inerrors.Isanderrors.As, for the same reason those functions handle singular unwrapping.In addition, this proposal provides a common method for the ecosystem to use to represent combined errors, permitting interoperation between third-party implementations.
How are multiple errors formatted?
A principle of the
errorspackage is that error formatting is up to the user. This proposal upholds that principle: Theerrors.Joinfunction combines error text with a user-provided separator, andfmt.Errorfwraps multiple errors in a user-defined layout. If users have other formatting requirements, they can still create their own error implementations.How do
IsandAsinteract with combined errors?Every major multierror package that I looked at (see "Prior art" below) implements the same behavior for
IsandAs:Isreports true if any error in the combined error matches, andAsreturns the first matching error. This proposal follows common practice.Does creating a combined error flatten combined errors in the input?
The
errors.Joinfunction does not flatten errors. This is simple and comprehensible. Third-party packages can easily provide flattening if desired.Should
Splitunwrap errors that wrap a single error?The
errors.Splitfunction could call the single-wrappingUnwrap() errormethod when present, converting a non-nil result into a single-element slice. This would allow traversing an error tree with only calls toSplit.This might allow for a small improvement in the convenience of code which manually traverses an error tree, but it is rare for programs to manually traverse error chains today. Keeping
Splitas the inverse ofJoinis simpler.Why does the name of the
Splitfunction not match theUnwrapmethod it calls?Giving the single- and multiple-error wrapping methods the same name neatly avoids any questions of how to handle errors that implement both.
Splitis a natural name for the function that undoes aJoin.While we could call the method
Split, or the functionUnwrapMultiple, or some variation on these options, the benefits of the above points outweigh the value in aligning the method name with the function name.Prior art
There have been several previous proposals to add some form of combining error, including:
https://go.dev/issue/47811: add Errors as a standard way to represent multiple errors as a single error
https://go.dev/issue/48831: add NewJoinedErrors
https://go.dev/issue/20984: composite errors
https://go.dev/issue/52607: add With(err, other error) error
fmt.Errorf("%w: %w", err1, err2) is largely equivalent to With(err1, err2).
Credit to @jimmyfrasche for suggesting the method name
Unwrap.There are many implementations of combining errors in the world, including:
https://pkg.go.dev/github.com/hashicorp/go-multierror (8720 imports)
https://pkg.go.dev/go.uber.org/multierr (1513 imports)
https://pkg.go.dev/tailscale.com/util/multierr (2 imports)