Hey, 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 trying to make a similar decision, and to help plan future APIs around multi-error situations.
We had a couple requirements in mind during this discussion: 1. It was all or nothing. Most users will expect symmetry between errors.Is 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 unless necessary. Knowledge of multierr is not part of the contract of a function 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 chain. 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 context. 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. That 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 new 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. Thanks. Abhinav 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 https://groups.google.com/d/msgid/golang-nuts/CAGeKYdWvNpYCX5B1hVKD%3D_ARYQ_YYya7uWF65TX7qbr1FXDWHA%40mail.gmail.com.