Commit Graph

28 Commits

Author SHA1 Message Date
Abhinav Gupta 616f4860ca
chore: Drop support for Go < 1.20 (#80)
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.
2023-10-11 14:46:30 -04:00
JacobOaks d8067ab003
Support multierr.Every (#78)
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.
2023-03-28 16:51:02 -04:00
Sung Yoon Whang faff69d9f0
Enable Errors support for any multi-error (#75)
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
2023-03-15 22:40:46 -07:00
Abhinav Gupta 2fd451dbf5
Drop external atomic dependency (#72)
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.
2023-02-14 22:35:24 -08:00
Abhinav Gupta 55bc553b48
Support Go 1.20 Unwrap() []error (#69)
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
2023-02-10 11:44:02 -08:00
Alexandr Burdiyan 4459990373
Add a shorthand for AppendInvoke (#65)
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>
2022-09-06 09:20:06 -07:00
Abhinav Gupta 80b07a745f
Document named return constraint for defer (#63)
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.
2022-08-12 09:28:55 -07:00
Abhinav Gupta 492b79245a
Test and lint against Go 1.19 (#62)
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
2022-08-12 09:14:35 -07:00
Tom Wieczorek f46d400df0
Use append instead of copy to clone slices (#58)
The append notation is more concise and should be more efficient, since
the target slice won't be initialized with zero values first.
2022-05-18 07:18:51 -07:00
Jesper Lindstrøm Nielsen cea7d2e1fc
Combine: Optimize for all nil (#55)
The current implementation of Combine returns the input slice in the
returned multierr. This causes it to escape to the heap unconditionally.
Optimizing for the no-errors case, we can copy the slice in that case
and keep it on the stack.

```
name                                old time/op    new time/op    delta
Combine/inline_1-8                    17.7ns ± 0%     2.1ns ± 0%   -88.20%  (p=0.008 n=5+5)
Combine/inline_2-8                    21.0ns ± 0%     4.4ns ± 1%   -79.03%  (p=0.008 n=5+5)
Combine/inline_3_no_error-8           24.4ns ± 0%     5.1ns ± 1%   -79.22%  (p=0.016 n=4+5)
Combine/inline_3_one_error-8          24.8ns ± 0%     5.4ns ± 1%   -78.42%  (p=0.008 n=5+5)
Combine/inline_3_multiple_errors-8    44.3ns ± 0%    54.9ns ± 0%   +23.80%  (p=0.008 n=5+5)
Combine/slice_100_no_errors-8         72.9ns ± 0%    73.4ns ± 1%    +0.68%  (p=0.008 n=5+5)
Combine/slice_100_one_error-8         74.5ns ± 0%    74.8ns ± 0%      ~     (p=0.056 n=5+5)
Combine/slice_100_multi_error-8        193ns ± 0%     193ns ± 0%      ~     (p=0.127 n=5+5)

name                                old alloc/op   new alloc/op   delta
Combine/inline_1-8                     16.0B ± 0%      0.0B       -100.00%  (p=0.008 n=5+5)
Combine/inline_2-8                     32.0B ± 0%      0.0B       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_no_error-8            48.0B ± 0%      0.0B       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_one_error-8           48.0B ± 0%      0.0B       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_multiple_errors-8     80.0B ± 0%     80.0B ± 0%      ~     (all equal)
Combine/slice_100_no_errors-8          0.00B          0.00B           ~     (all equal)
Combine/slice_100_one_error-8          0.00B          0.00B           ~     (all equal)
Combine/slice_100_multi_error-8        64.0B ± 0%     64.0B ± 0%      ~     (all equal)

name                                old allocs/op  new allocs/op  delta
Combine/inline_1-8                      1.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)
Combine/inline_2-8                      1.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_no_error-8             1.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_one_error-8            1.00 ± 0%      0.00       -100.00%  (p=0.008 n=5+5)
Combine/inline_3_multiple_errors-8      2.00 ± 0%      2.00 ± 0%      ~     (all equal)
Combine/slice_100_no_errors-8           0.00           0.00           ~     (all equal)
Combine/slice_100_one_error-8           0.00           0.00           ~     (all equal)
Combine/slice_100_multi_error-8         2.00 ± 0%      2.00 ± 0%      ~     (all equal)
```

Co-authored-by: Abhinav Gupta <abg@uber.com>
2022-02-28 12:01:14 -08:00
Abhinav Gupta d49c2ba574
doc: AppendInvoke: Fix typo (#54)
One of the examples for AppendInvoke used `AppendInvoke(err, ...)` but
the argument is supposed to be a `*error`. Fix the example.
2021-07-12 10:57:28 -07:00
Abhinav Gupta 520752cf5f Update license headers everywhere
License headers should be a range, not just the current year.
2021-05-06 10:23:05 -07:00
baez90 a402392041
Add AppendInvoke (#47)
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>
2021-05-06 08:13:15 -07:00
Tsuji Daishiro e015acf18b
error.go: Fix typo of doc (#44) 2020-11-24 10:20:17 -08:00
Abhinav Gupta 0eb6eb5383
Drop Go 1.12 support (#41)
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.
2020-10-27 15:00:01 -07:00
George Xie 4a27324ebb
docs: Add missing value in example (#37)
Fix print statement in example to actually include the combined error
object.
2020-04-23 10:01:20 -07:00
Arthur Befumo 0603dab385 docs: multierr.Errors returns nil, not empty (#33)
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`.
2019-11-08 10:29:00 -08:00
Abhinav Gupta 60a318af5f
Add AppendInto function (#31)
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
2019-11-04 10:23:04 -08:00
Abhinav Gupta bd075f90b0 error.go: Remove unused variable 2019-04-29 14:04:58 -07:00
ferhat elmas ddea229ff1 Append: Fix name in example (#25)
The example was using Combine instead of Append.
2018-01-22 09:25:45 -08:00
Abhinav Gupta 8aa0ee0efd Support extracting underlying error list (#19)
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.
2017-06-27 10:54:19 -07:00
Abhinav Gupta a100c982ca Add more package-level docs (#17)
This adds some more basic package level documentation to the library.
This will be extended with some upcoming changes.
2017-06-27 09:59:12 -07:00
Abhinav Gupta a8435ed7b2 Graceful handling of nil errors (#16)
We shouldn't panic if *multiError is nil.
2017-06-27 09:58:54 -07:00
Prashant Varanasi 8deb4d8f84 Avoid allocating a new slice on every Append (#9)
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.
2017-04-10 15:02:29 -07:00
Prashant Varanasi 91b99f42e5 Fix Append modifying a possibly shared array (#8)
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.
2017-03-27 10:54:46 -07:00
Abhinav Gupta de6740a55f formatting: %+v should apply to nested errors (#5)
`%+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()`.
2017-03-24 14:27:19 -07:00
Abhinav Gupta a0f768a1e1 Lint fixes (#4)
This fixes linter issues and adds a minimal package-level doc.
2017-03-22 18:16:12 -07:00
Abhinav Gupta 11c3348c27 Initial import (#1)
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).
2017-03-22 17:05:00 -07:00