Drops support for versions of Go older than 1.20.
With 1.20 being the minimum supported Go version,
we can remove the pre_go120 code, and merge the post_go120 code.
With Go 1.20's multi-error interface, our errorGroup interface
is not necessary, but we need it for backwards compatibility.
Also updates the documentation to suggest Go 1.20's interface
instead of ours.
The `Errors() []error` function is now an implementation detail.
This PR introduces a new exported function in the multierr package
called `Every`, which checks every error within a given error against a
given target using `errors.Is`, and only returns true if all checks
return true.
```go
err := multierr.Combine(ErrIgnorable, errors.New("great sadness"))
fmt.Println(errors.Is(err, ErrIgnorable)) // output = "true"
fmt.Println(multierr.Every(err, ErrIgnorable)) // output = "false"
err := multierr.Combine(ErrIgnorable, ErrIgnorable)
fmt.Println(errors.Is(err, ErrIgnorable)) // output = "true"
fmt.Println(multierr.Every(err, ErrIgnorable)) // output = "true"
```
This also works when the error passed in as the first argument is not a
`multiErr`, including when it is a go1.20+ error that implements
`Unwrap() []error`.
This solves #66.
Starting Go 1.20, any multi-error should conform to the standard unwrap
method: Unwrap() []error.
This changes multierr.Errors() method to support any error that complies
to that interface.
Fix#70 / GO-1883
Now that this package only supports Go 1.19 and 1.20,
it can make use of the atomic.Bool [1] added in Go 1.19.
[1] https://pkg.go.dev/sync/atomic#Bool
This has the effect of eliminating any external dependencies
from this package -- minus testify, which is only used for testing.
Go 1.20 includes native support for wrapping multiple errors.
Errors which wrap multiple other errors must implement,
Unwrap() []error
If an error implements this method, `errors.Is` and `errors.As`
will descend into the list and continue matching.
Versions of Go prior to 1.20, however, still need the old
`Is` and `As` method implementations on the error object
to get a similar behavior.
This change adds the `Unwrap() []error` method
gated by a build constraint requiring Go 1.20 or higher.
It similarly moves the existing `Is` and `As` methods
to a file that is ignored on Go 1.20 or higher.
Once Go 1.21 is released and 1.19 is no longer supported,
the pre-go1.20 file may be deleted and the build constraints removed.
For details, see also the section,
"How should existing multierror types adopt the new interface?"
of the [multiple errors proposal][1].
[1]: https://github.com/golang/go/issues/53435
Refs #64
I really like the idea of catching returned errors from deferred functions. Though having to use `multierr` package name twice in the same line makes it a bit verbose in many occasions.
This PR introduces a shorthand for AppendInvoke which allows passing function or method value directly without wrapping it into an Invoker.
So this:
```go
defer multierr.AppendInvoke(&err, multierr.Invoke(my.StopFunc))
```
could become this:
```go
defer multierr.AppendFunc(&err, my.StopFunc)
```
Co-authored-by: Sung Yoon Whang <sungyoonwhang@gmail.com>
As discussed in #61, if you attempt to use defer multierr.AppendInvoke
with an error that is not a named return, the system will lose the
error.
func fails() error { return errors.New("great sadness") }
func foo() error {
var err error
defer multierr.AppendInvoke(&err, multierr.Invoke(fails))
return err
}
func main() {
fmt.Println(foo()) // nil
}
https://go.dev/play/p/qK4NR-VYLvo
This isn't something the library can address because of how defers work.
This change adds a warning about the error variable being a named return
in all places where we suggest use of multierr with defer.
While we're at it, this makes use of the new [Foo] godoc syntax to
generate links to other functions in the package in "See Foo"
statements in the documentation.
Add Go 1.19 to the test matrix, and switch linting to Go 1.19 since we
prefer to lint with the latest stable release.
With Go 1.19, we need to:
Update tools dependencies
Fix any use of ioutil
gofmt all files to match new godoc format
Add the following APIs:
type Invoker
type Invoke // implements Invoker
func Close(io.Closer) Invoker
func AppendInvoke(*error, Invoker)
Together, these APIs provide support for appending to an error from a
`defer` block.
func foo() (err error) {
file, err := os.Open(..)
if err != nil {
return err
}
defer multierr.AppendInvoke(&err, multierr.Close(file))
// ...
Resolves#46
Co-authored-by: Abhinav Gupta <abg@uber.com>
Support for `errors.Is` and `errors.As` was special-cased to Go 1.13 or
higher. This is no longer necessary since, as of right now, only Go 1.14
and 1.15 need to be supported.
Previously the docs incorrectly stated that `multierr.Errors(err)`
would return an empty slice rather than a nil slice, which would imply
that `multierr.Errors(nil) == []error{}`, which isn't true.
This updates the docs to reflect reality: `multierr.Errors(nil) == nil`.
This adds an AppendInto function that behaves similarly to Append
except, it operates on a `*error` on the left side and it reports
whether the right side error was non-nil.
func AppendInto(*error, error) (errored bool)
Making the left side a pointer aligns with the fast path of `Append`.
Returning whether the right error was non-nil aligns with the standard
`if err := ...; err != nil` pattern.
```diff
-if err := thing(); err != nil {
+if multierr.AppendInto(&err, thing()) {
continue
}
```
Resolves#21
This change adds an `Errors(err) []error` function which returns the
underlying list of errors. Additionally, it amends the contract for
returned errors that they MAY implement a specific interface.
This is needed for Zap integration as per discussion in #6.
Resolves#10.
Currently, appending a non-nil error to a multierror always copies the underlying slice, since there may be multiple goroutines appending to the same multierr,
```go
merr := multierr.Append(errors.New("initial"), errors.New("multierr"))
go func() {
err1 = multierr.Append(merr, errors.New("1"))
[..]
}()
go func() {
err2 = multierr.Append(merr, errors.New("2"))
[..]
}()
```
However, the common use case is a standard for loop:
```go
var merr error
for _, v := range values {
merr = multierr.Append(merr, processValue(v))
}
return merr
```
Since there is only a single resulting slice, we don't need to create a full copy on every `Append`. Instead, we track whether we have modified the underlying slice from `Append` already using an atomic boolean, and only create a copy if the underlying slice has been modified.
It's possible to have a multiError that is appended to multiple times
causing an underlying array to be shared and end up getting modified.
For now, remove the special case for `append(multiError, err)` which can
cause this bug.
`%+v` on a multierr should use `%+v` on the nested errors. That is,
fmt.Sprintf("%+v", multierr.Combine(err1, err2))
Should use `fmt.Sprintf("%+v", err1)` and `fmt.Sprinf("%+v", err2)` in
the output rather than `err{1, 2}.Error()`.
This is the initial import of the `multierr` library. The library
intends to provide a single common implementation of the `multierr`
type.
The implementation is slightly more complex than you would expect
because it aims to be zero-alloc for the hot-path (no errors or no
nested multierrors).