-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: context: context.Cause should return a wrapped error #63759
Comments
@mitar I have done something very similar in my own libraries, but instead of adding an Perhaps |
Yes, when you control errors that works. But for something in stdlibrary you want effectively the behavior of
Yes, that could also be an option, that instead of implementing custom Either way I am fine. This is a trivial helper function which primarily handles the case that blindly calling |
I was thinking along the lines of if type CauseError struct {
cause error
}
func (err CauseError) Error() string {
return err.cause.Error()
}
func (err CauseError) Unwrap() []error {
return []error{context.Canceled, err.cause}
} My point being is that creating a custom error to implement the Is/As interfaces opens up a lot more complexity than simply unwrapping both errors. This way Is and As work as intended and it is clearer. We don't even need to use |
I think we generally have the same idea in mind and are discussing now a very particular implementation detail. Personally, for wrapping two errors, I would prefer (difference in type CauseError struct {
cause error
}
func (err *CauseError) Error() string {
return fmt.Sprintf("%s: %s", context.Canceled.Error(), err.cause.Error())
}
func (err *CauseError) Unwrap() []error {
return []error{context.Canceled, err.cause}
} But I do think the following is cleaner: type CauseError struct {
cause error
}
func (err *CauseError) Error() string {
return fmt.Sprintf("%s: %s", context.Canceled.Error(), err.cause.Error())
}
func (err *CauseError) Is(target error) bool {
return target == context.Canceled || errors.Is(err.cause, target)
}
func (err *CauseError) As(target interface{}) bool {
return errors.As(context.Canceled, target) || errors.As(err.cause, target)
}
func (err *CauseError) Unwrap() error {
return err.cause
} |
type CauseError struct {
cause error
} Is there any reason not to export the |
I do like the Overall I support this proposal.
There is no particular reason to not export it. Regardless to get to the field you will need to |
I think this proposal is technically backwards incompatible because people could have written code like so: ctx, cancel := context.WithCancelCause(context.Background())
cancel(SentinalError)
if context.Cause(ctx) == SentinalError {
// handle sentinal error cause
} That being said, it is now well-known that people should be using errors.Is/As and it would be nice to fix/improve this API with a little dash of hindsight, but is this a deal breaker? |
That why in my proposal I also have option that we implement yet another function which I tentatively call
I think it is better to provide cause through |
We can't change how That makes the question whether this is enough of a problem that it's worth introducing yet another way to get an error out of a |
(I previously made a comment about this which I am now turning into full proposal.)
I have been following the #51365 issue for some time and was really happy that it landed. But now I tried to finally update code to use the new API and I was a bit surprised by it, primarily that it makes it hard to work with logic which expects that the error is
context.Canceled
and at the same time be able to retrieve the error at the end.What I mean is that I have code which 1) obtains the error from the context, then it 2) passes it through by returning the error through a chain of function call returns, and then 3) inspects the error and determines that it is about the context cancellation and wants to obtain the cause for it (e.g., log it).
Now, the tricky thing is that at point 3) I cannot know both if the error is context cancellation and what was the cause. I can do that only at point 1).
Options to me seems to be:
errors.Join(ctx.Err(), context.Cause(ctx))
, but this is ugly if there was no cause, becausecontext.Cause(ctx) in that case returns the same as
ctx.Err()`, so I am joining two same errors. So more logic to combine only if they are different. Also returned error message is ugly.gitlab.com/tozd/go/errors
's WrapWith which was made so that one error can be made a cause of another error, without modifying either of them. The error message is nicer, but the logic to check if they are different has still to be added.I understand that originally
ctx.Err
could not return ancontext.Canceled
error which would unwrap to the cause error to assure thaterr == context.Canceled
continues to work. But why not makecontext.Cause
return such an error already? So in my view, it would be better ifcontext.Cause
returned:ctx.Err
if there is no cause.context.Is(context.Canceled)
and can be unwrapped to the cause error. This can be done by having a customIs
method on the error.For
context.Cause
returned errors we do not have to assure thaterr == context.Canceled
holds.I am not sure if we can still change
context.Cause
now? Probably not? But could we then introduce yet anothercontext.CauseError
or something, which would return an error with the behavior above? It can probably be made in 3rd party library as well, it is not too long, but I think it would be great to have it in standard library.The text was updated successfully, but these errors were encountered: