go: errors: ensure the 1.13 method to access the cause is called Cause() not Unwrap()
The rationale in https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md mentions this:
Unwrap. It is unclear if Unwrap is the right name for the method returning the next error in the error chain. Dave Cheney’s
github.com/pkg/errors
has popularized Cause for the method name, but it also uses Cause for the function that returns the last error in the chain. At least a few people we talked to did not at first understand the subtle semantic difference between method and function. An early draft of our design used Next, but all our explanations referred to wrapping and unwrapping, so we changed the method name to match.
In summary the rationale was “some people were confused so we’re going to change the name”.
However this choice creates a more serious technical problem.
Suppose a program upgrades to use Go 1.13 but has some dependency library that relies on github.com/pkg/errors
(or another Cause()
-compatible error library). Now consider what happens if our program makes some API call through its dependency, and that encounters an error from the standard library – for example io.EOF
or context.DeadlineExceeded
. And then the intermediate library dependency wraps the error using the facilities in github.com/pkg/errors
which only provides Cause()
.
How is the program now meant to identify the ultimate cause of its error?
The proposal with Is()
and Unwrap()
will be unable to “peek through” the wrappers created by the existing library code. This is defective design.
Solution: By changing the design in Go 1.13 to instead make the causer access method remain Cause()
, the new 1.13 error package would become/remain drop-in compatible with existing library error wrapper code.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 5
- Comments: 19 (12 by maintainers)
It is not a goal of the proposal to be compatible with existing error packages. Indeed, that is impossible, since they contradict each other. The goal is to establish a common set of names and behaviors, and the hope is that existing packages like
pkg/errors
will adopt them.Couldn’t you continue to use
Cause
? Leaving aside the fact that you should never wrapio.EOF
, you could writeThank you for the example, although I believe it is disingenuous: this exemplifies specifically the feature of that package that hides the cause. The goal of that
Note
API is to provide control for the code that constructs the error on the ability for other downstream code to query that cause. That use case / requirement would exist also for users ofxerrors
and arguably anxerror
-compatible implementation could provide the same feature that prevents a cause from being visible viaUnwrap()
.So far it does not seem you have provided evidence that in the common case (even a common case for
gopkg.in/errgo.v2/errors
) the established ecosystem is incompatible with the Go 1.13 proposal, and it does not seem that the conversation so far has provided a reason to keep the current design that outweighs the inconvenience that I have outlined initially.Yes I am aware I could do this. However, if I and other people find ourselves required to do this often (as I am arguing since the beginning of the thread), this would be strong evidence that the proposed definition of Go’s error API is defective. Now (before 1.13 is released) would be an adequate time to study this risk and perhaps make adjustments before the Go user community finds themselves inconvenienced by the defect.
You realize that you could change the go 1.14/xerror code to use Cause instead and take the cost of the change for yourself, instead of pushing it to the community right?
So far the discussion has been pretty clear:
there’s no real good reason to keep Unwrap over Cause other than choosing who to inconvenience the most
in terms of number of people inconvenienced you are choosing the maximum amount of inconvenience because… I guess it makes someone’s life easier, consequences be dammed? – Sent from my Android device with K-9 Mail. Please excuse my brevity.
I would expect third party packages like github.com/pkg/errors to change their
Cause
function to support both their ownCause
method andUnwrap
. That seems like the way to migrate from github.com/pkg/errors to xerrors to Go1.14.As a matter of practicality,
golang.org/x/xerrors
usesUnwrap()
, and there are people using that package now. Which means that whether we end up choosingUnwrap()
orCause()
or something else, everyone else is going to have to add backward compatibility,If we choose
Unwrap()
, thengithub.com/pkg/errors
and everyone else will have to add support forUnwrap()
.If we choose
Cause()
, thengolang.org/x/xerrors
and everyone else will have to add support forCause()
.And ditto for any other API that is out there.
I understand that the error values proposal isn’t final yet, but I think it would be impractical to change it drastically without a strong reason since people are using it already (including me). Similarly, there was no guarantee that
github.com/pkg/errors
provided any kind of standard API across the Go ecosystem and their users should have understood that they were committing to an API only implemented bygithub.com/pkg/errors
and any explicitly cooperating packages.The point of this part of the error values proposal, as I see it, was to commit to a standard API across the Go ecosystem. That it is not the
github.com/pkg/errors
API is perhaps unfortunate, but at this point in time being noncommittal about the API will cause more harm than benefit.Addressing @knz’s concern directly,
The error values proposal doesn’t change the fact that every package needs to document an explicit contract about how a user will go about unwrapping or checking errors, or the backward compatibility implications thereof. All it does is adds a standard contract that hopefully all packages can agree upon going forward.
Our final decision is to stay with
Unwrap
.@knz please be respectful.
There exist error implementations where the
Cause
method returns the root of the chain, as well as ones whereCause
returns the next step in the chain. There is no way to reconcile these two definitions.Was this issue a reason for revert at 3e2c522d5c712fa2b1d18a101272abefc7dcb074?
Edit after @jba’s answer below. Found it: https://github.com/golang/go/issues/29934#issuecomment-489682919
I am really glad that you recognize that some people are going to be inconvenienced.
It would be interesting here, for the sake of clarity and methodology, to estimate the size of the user populations. AFAIK
xerrors
has been around for less time than the alternatives usingCause()
.There is a utility argument to be made about the amount of inconvenience. A solution that causes less inconvenience in aggregate seems (to me) to be the better solution. I am sure that the fact that you happen to be invested in the initial development of
xerrors
will not bias you against looking at the utility aspect as a whole.You are mistaken: https://play.golang.org/p/1rR97pPojOd