Thanks for your response. Although we were unsure about this at first (which
is why we started this thread), after further discussion we agree that this
API makes sense. Part of the discussion is summarized below for others
to make a similar decision, and to help plan future APIs around multi-error

We had a couple requirements in mind during this discussion:

   1. It was all or nothing. Most users will expect symmetry between
   and errors.As. That is, if errors.Is(err, target) succeeds,
   errors.As(err, &targetType) should also succeed.
   2. Users should not know that they are dealing with a multierr error
   necessary. Knowledge of multierr is not part of the contract of a
   returning error unless explicitly documented. So introducing new
   functions to the multierr package for errors.Is/As was to be avoided
   for now.

The first issue we discussed was what users expect when they call
errors.Is(err, cause) where err is a multierr error (but the user does not
know that). Does the user expect a match if *all* errors inside err match,
or if *any* error inside err matches? Valid scenarios are conceivable in
both directions. We agreed that there were more cases for the “any error may
match” route than the “all errors must match.”

The second issue was around loss of information with errors.As when the
error is a multierr error. For non-multierr errors, when extracting an error
with errors.As, you will usually have one instance of each error type in the

fmt.Errorf("something went wrong: %w", myError{Cause: net.OpError{Err: ..}})

It will be uncommon to have, myError{...{Err: myError{...}}} where myError
is meaningful. So the “first match succeeds” behavior implemented by
errors.As suffices for most cases.

With multierr errors, it will be more common to have multiple error chains
with the same wrapper type as these will be commonly produced from the same

err = multierr.Combine(
    fooFailed{Cause: net.OpError{..}},
    fmt.Errorf("...: %w", fooFailed{Cause: context.DeadlineExceeded}),
    net.OpError{Err: fooFailed{cause: os.ErrNotExist}},

Returning just the first match with errors.As seems like a significant loss
of information, and non-deterministic if the error list is constructed
non-deterministically (from multiple concurrent operations, for example).

We think that the ideal behavior here would be for errors.As to produce a
view of the original multierr error, filtered down to the requested type.
is, something roughly equivalent to the following:

// var target fooFailed
// errors.As(err, &target)

*target = multierr.Combine(
    fooFailed{Cause: net.OpError{..}},
    fooFailed{Cause: context.DeadlineExceeded},
    fooFailed{cause: os.ErrNotExist},

// or,
//  var target []fooFailed

But that’s impossible to do cleanly with the current design of errors.As:
target is expected to be of type fooFailed, not multierr‘s error type.

Eventually we decided that the loss of information with errors.As, although
regrettable, is acceptable in the short term while we experiment with the
functionality internally.

We may also end up bending requirement (2) above and implement a
multierr.Find API that extracts a slice of matching error objects by type,
but we’d like to experiment with just the basic functionality first.

In conclusion, we decided that the PR on multierr is fine as-is. We’ll merge
and release it in the near future.

We hope this discussion was helpful to others, and perhaps provided
inspiration for future APIs around multi-error use cases.



On Thu, Sep 5, 2019 at 4:35 PM Mitchell Hashimoto <xmit...@gmail.com> wrote:

> Hi,
> On Tuesday, September 3, 2019 at 8:29:36 PM UTC-7, Abhinav Gupta wrote:
>> The PR we have open
>> <https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_uber-2Dgo_multierr_pull_28&d=DwMFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=2o4zfwfBjstp3IOw1zbMmTVguWA_EGDyOXTmFJRMsO0&e=>
>> implements the following behavior:
>>    - For errors.As
>> <https://urldefense.proofpoint.com/v2/url?u=http-3A__errors.As&d=DwQFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=fw8ZxOAFF4gUTr6P3lDvN-Tz9UytZ5EWY7rqECq4rKA&e=>,
>>    the first error in the list where errors.As
>> <https://urldefense.proofpoint.com/v2/url?u=http-3A__errors.As&d=DwQFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=fw8ZxOAFF4gUTr6P3lDvN-Tz9UytZ5EWY7rqECq4rKA&e=>
>>    succeeds is
>>    returned.
>>    - For errors.Is
>> <https://urldefense.proofpoint.com/v2/url?u=http-3A__errors.Is&d=DwQFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=K4jH4nigimLFOGHFe7ZHpv2vPsP3G0jSZYx4lxjPUo0&e=>,
>>    all errors are checked until one matches, in which case we
>>    succeed. If none of the errors matched, we fail.
>> This means that if you have multiple os.PathErrors, you can use errors.As
>> <https://urldefense.proofpoint.com/v2/url?u=http-3A__errors.As&d=DwQFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=fw8ZxOAFF4gUTr6P3lDvN-Tz9UytZ5EWY7rqECq4rKA&e=>
>> to extract the first of those, and you can use errors.Is
>> <https://urldefense.proofpoint.com/v2/url?u=http-3A__errors.Is&d=DwQFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=K4jH4nigimLFOGHFe7ZHpv2vPsP3G0jSZYx4lxjPUo0&e=>
>> to match for
>> equality against any of those.
>> What we’d like to ask the community and the Go Developers is whether this
>> behavior is what you would expect from a combined error tree like this.
> At HashiCorp, we have our own lib go-multierror
> <https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_hashicorp_go-2Dmultierror&d=DwMFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=ABvwiCv6h3rCtuV1wwQLf4xUM5Tlm_qfBxtaRDaQ_8Q&e=>
>  and
> we were recently discussing the same thing. So thank you for opening this
> thread and appealing to the broader Go community.
> We came to the same conclusions as you did, to the exact behavior. It
> seems the most reasonable to me given the current interface definitions.
> So I just want to say I agree, and would love to hear from anyone else how
> they feel.
> Best,
> Mitchell
>> Thanks.
>> Abhinav
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/4af1fe63-bdc6-484b-a05f-49667f89f86d%40googlegroups.com
> <https://urldefense.proofpoint.com/v2/url?u=https-3A__groups.google.com_d_msgid_golang-2Dnuts_4af1fe63-2Dbdc6-2D484b-2Da05f-2D49667f89f86d-2540googlegroups.com-3Futm-5Fmedium-3Demail-26utm-5Fsource-3Dfooter&d=DwMFaQ&c=r2dcLCtU9q6n0vrtnDw9vg&r=1l8pq-0AmiqF9VqB_zc2sA&m=CKRSrDFZHM-_UTL6eD3X3kNJUooF67kqYEah6XCYyQk&s=wwKi9FtAa9VZD83UZoSk-zqPW3dj-wvaAByu5VZhjBY&e=>
> .

You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 

Reply via email to