pkg / errors

Simple error handling primitives
https://godoc.org/github.com/pkg/errors
BSD 2-Clause "Simplified" License
8.18k stars 691 forks source link

Unwrap doesn't return the base error #223

Open yaziine opened 4 years ago

yaziine commented 4 years ago

Wrapping an error with errors.Wrap, unwrapping it with errors.Unwrap returns the whole error and not the base one. Is it intentional?

https://play.golang.org/p/baYpfrrvN35

davecheney commented 4 years ago

errors.Unwraps only unwraps errors wrapper with errors.Wrap, not fmt.Errorf

yaziine commented 4 years ago

Not sure to understand you here. Do you mean that errors.Unwraps only unwraps errors wrapped with fmt.Errorf instead?

davecheney commented 4 years ago

Unwrap is just a wrapper around the stdlib errors package's Unwrap.

https://github.com/pkg/errors/blob/master/go113.go

aperezg commented 4 years ago

As @davecheney said the behaviour of the Unwrap in the pkg/errors is the same that the standard library:

https://play.golang.org/p/73xe4gnoXKz

puellanivis commented 4 years ago

Oh, this might be the source of the confusion: Wrap(…) returns a &withStack{ &withMessage{ … } } and so when one calls Unwrap() on it, one gets the &withMessage{ … } rather than the original message that was put into Wrap(…), which makes it appear like it is not unwrapping, unless you’re printing stack traces: https://play.golang.org/p/x0JN-W_FStu

This ends up being misleading because one would naturally expect err2 := errors.Unwrap(errors.Wrap(err1)) would result in err2 == err1 but it does not, because internally errors.Wrap is actually double wrapping. No one noticed when using Cause(…) because it kept unwrapping until it got to a non-Causer, but errors.Unwrap only unwraps a single layer. :confused:

zhangguanzhang commented 4 years ago

should use errors.Cause(err) https://play.golang.org/p/L-uJU07lCAu

puellanivis commented 4 years ago

errors.Cause will repeatedly unwrap all of the pkg/errors wrappings. So we get:

err := stdErrors.New("thing")
err2 := errors.WithMessage(err, "inner wrap")
err3 := errors.Wrap(err, "outer wrap")
cause := errors.Cause(err3)
unwrap := errors.Unwrap(err3)
// (cause == err) == true
// (unwrap != err3 && unwrap != err2 && unwrap != err) == true

There is no way in this scenario to retrieve err2 from err3, and that’s not ideal.