-
Notifications
You must be signed in to change notification settings - Fork 700
Cause() vs RootCause() #18
Comments
Would the below not work to achieve inspecting the underlying error? This library shouldn't change that. if err != nil {
// From the README
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
} Replace |
Yes, I could imagine a usecase where I want to be able to check the most recent error, not the root cause. Lets say A takes function B and calls it. If B returns an error, A wraps it and returns. If I call A and want to get error returned from B and inspect it, there is no easy way. Error from B may itself be a wrapper around some other error - we might not know anything about implementation of B, just the signature, but we might know its "contract", know that it returns enhanced errors with additional information (and may also have |
Because these are wrappers around the original error, the cause. It may have been a poor choice of names, but I cannot change it now.
Yup, but I'm not sure what you're expecting to discover as all the error values returned from this package are anonymous. |
Yes, if using this package's i.e. this package adds |
You don't have to use
|
Yes, that is what I'm saying. But then |
I've been wondering about this question as well. There is an asymmetry between Wrap and Cause. Wrap builds up anonymous layers of error context, but errors.Cause could strip away layers not created by Wrap—layers that are external to this package—if those error types implement This asymmetry could be resolved by adding this function. // Unwrap removes annotations added by Wrap from err until it
// finds an error value not created by Wrap which it then returns.
func Unwrap(err error) error |
@ChrisHines I agree it asymmetric. Can you give me an example on what you would do with the value from Unwrap? |
@ash2k thank you for clarifying. At the moment there are two, incomplete, ways you can do this with the errors package. The first is to write your own The second is to use something like To give two pieces of information, possibly justifications, i'm not sure.
|
As far as I understand from slides you are suggesting to inspect error values for the behaviour, not for some type/value but what I'm trying to say is that currently this package does not provide means to get the cause of the error (to let me inspect it for behaviour). package x
type isMagic interface {
IsMagic() bool
}
type myError struct {
cause error
message string
time time.Time
dayOfMonth int
isMagic bool
}
func (e *myError) IsMagic() bool { return e.isMagic }
func (e *myError) Cause() error { return e.cause }
func (e *myError) Error() string { return fmt.Sprintf("%v(%d): %s", e.time, e.dayOfMonth, e.message) }
func IsMagic(e error) bool {
m, ok := e.(isMagic)
return ok && m.IsMagic()
}
// B returns errors that have IsMagic() method. Check using IsMagic() function.
func B() (int, error) {
return 0, &myError{errors.New("Cause"), "msg", time.Now(), 0, true}
} Then func A(b func() (int, error)) int, error {
n, err := b()
if err != nil {
return 0, errors.Wrap(err, "Boom")
}
// Some other operation here, returning non-magic error
err = ....
if err != nil {
return 0, errors.Wrap(err, "Big boom")
}
...
return n+10, nil
}
a, err := A(x.B)
if err != nil {
e := errors.CauseOfError(err) // Cannot do this
if x.IsMagic(e) {
// Magic!
}
} I.e. I don't see how what I'm asking about contradicts your philosophy :) |
I believe it does, but we disagree on the definition of the cause of an error. This package defines the cause as the first |
package main
import (
"fmt"
"time"
"errors"
pkgErr "github.com/pkg/errors"
)
type isMagic interface {
IsMagic() bool
}
type myError struct {
cause error
message string
time time.Time
dayOfMonth int
isMagic bool
}
func (e *myError) IsMagic() bool { return e.isMagic }
func (e *myError) Cause() error { return e.cause }
func (e *myError) Error() string { return fmt.Sprintf("%v(%d): %s", e.time, e.dayOfMonth, e.message) }
func IsMagic(e error) bool {
m, ok := e.(isMagic)
return ok && m.IsMagic()
}
// B returns errors that have IsMagic() method. Check using IsMagic() function.
func B() (int, error) {
return 0, &myError{errors.New("Cause"), "myErrorOfMagic", time.Now(), 0, true}
}
func A(b func() (int, error)) (int, error) {
n, err := b()
if err != nil {
return 0, pkgErr.Wrap(err, "Boom")
}
// Some other operation here, returning non-magic error
err = errors.New("Something is wrong")
if err != nil {
return 0, pkgErr.Wrap(err, "Big boom")
}
return n+10, nil
}
func main() {
a, err := A(B)
if err != nil {
e := DirectCause(err)
//e := pkgErr.Cause(err)
if IsMagic(e) {
fmt.Printf("%v is a magic error", e)
} else {
fmt.Printf("%v is not a magic error", e)
}
return
}
fmt.Println(a)
}
type causer interface {
Cause() error
}
func DirectCause(err error) error {
cause, ok := err.(causer)
if ok {
return cause.Cause()
}
return err
} Yes, our definitions of "cause" are different. Sorry for confusion.
|
Thank you for your reply. wrt to question one, see #21 for a counter suggestion. wrt 2. This package does implement |
I'm a Go newbie so I might be overlooking something. Sorry if that is the case. |
@ash2k I'm sorry but I will not be making either of those types public.
|
@davecheney It makes sense if we look at this package as at just a random package. But if the end goal is to build something standard, make it part of standard library then it may make sense to think about the usecase I'm describing. If the code is already in the package anyway and the package is part of std. lib AND I'm using it anyway ( |
I don't really know what to say. The design of this package is based on If I understand your objections, you want more functionality embedded in Please accept my apologies on advance if I have misunderstood your On Wed, 11 May 2016, 14:05 Mikhail Mazurskiy, [email protected]
|
@ash2k, check out how hashicorp/errwrap does it. The gist is that error wrapping forms a linked list (just like in this package; cause is the pointer to the next "node" so to speak), and the list is traversable via a public interface (like "Causer") with the terminal node being the first error to not implement the interface or to return no wrapped error. This allows adding structured/typed information to errors without erasing the underlying error, and to be able to do it recursively. In addition, the package provides many helpers for walking the error list, so you're able to get to an underlying error if necessary. The package ansel1/merry takes this same pattern to an extreme; each "node" looks like this: type node struct {
key, value interface{}
err error
} So the "list" basically becomes a map. This strategy has its pros and its cons, but I'm not sure which I prefer yet. |
Related proposed solution: #144 |
This is closed for good reason. You can type assert for an |
I just came across this package and wondering why package's
Cause()
function returns the root cause and not just the cause of the passed error value?I can imagine a situation where I want to inspect the cause of the error and check for some additional information in it, etc. So, to get that information I'll have to do a check for
Cause()
presence myself, call it, do a type check for additional information methods and only then call them. An additional method that returns the cause might be useful here.ps. I wonder why this is not part of the standard library. Seems to be a very useful pattern.
The text was updated successfully, but these errors were encountered: