10

I am checking through an error trace in Go v1.13 Go v1.14. Why does it appear that only error implementations without parameters or with value receivers can be found with errors.Is()? This means that an error implementation capable of wrapping must have a value receiver in order to be able to be found with errors.Is().

package main import ( "fmt" "errors" ) type someAtomicError struct {} func (e *someAtomicError) Error() string { return "Hi!" } func checkAtomicError() { e := &someAtomicError{} e2 := fmt.Errorf("whoa!: %w", e) e2IsE := errors.Is(e2, &someAtomicError{}) fmt.Println("atomic error trace ---\t\t", e2, "\t\t--- is traceable: ", e2IsE) } type someWrapperError struct { Msg string Err error } func (e someWrapperError) Error() string { return fmt.Sprintf("%s: %v", e.Msg, e.Err) } func (e someWrapperError) Unwrap() error { return e.Err } func checkWrapperError() { e := someWrapperError{"Hi!", nil} e2 := fmt.Errorf("whoa!: %w", e) e2IsE := errors.Is(e2, someWrapperError{"Hi!", nil}) fmt.Println("wrapper error trace ---\t\t", e2, "\t--- is traceable: ", e2IsE) } type somePointerWrapperError struct { Msg string Err error } func (e *somePointerWrapperError) Error() string { return fmt.Sprintf("%s: %v", e.Msg, e.Err) } func (e *somePointerWrapperError) Unwrap() error { return e.Err } func checkPointerWrapperError() { e := &somePointerWrapperError{"Hi!", nil} e2 := fmt.Errorf("whoa!: %w", e) e2IsE := errors.Is(e2, &somePointerWrapperError{"Hi!", nil}) fmt.Println("pointer wrapper error trace ---\t", e2, "\t--- is traceable: ", e2IsE) } func main() { checkAtomicError() checkWrapperError() checkPointerWrapperError() } //atomic error trace --- whoa!: Hi! --- is traceable: true //wrapper error trace --- whoa!: Hi!: <nil> --- is traceable: true //pointer wrapper error trace --- whoa!: Hi!: <nil> --- is traceable: false 

https://play.golang.org/p/-hSukZ-gii2

It seems that any difference in parameters, including in the wrapped error parameter, Err, will result in the type being unable to be found with errors.Is().

2 Answers 2

14

The reason you are getting false while trying to find one error within another via errors.Is is because - while the two errors may have the same field values - they are two different memory pointers:

e := &somePointerWrapperError{"Hi!", nil} e2 := &somePointerWrapperError{"Hi!", nil} // e2 != e ew := fmt.Errorf("whoa!: %w", e) errors.Is(ew, e) // true errors.Is(ew, e2) // false - because `ew` wraps `e` not `e2` 

So how to detect this "type" of error and get its value: use errors.As instead:

e := &somePointerWrapperError{"Hi!", nil} e2 := fmt.Errorf("whoa!: %w", e) var ev *somePointerWrapperError if errors.As(e2, &ev) { fmt.Printf("%#v\n", ev) // &somePointerWrapperError{Msg:"Hi!", Err:error(nil)} } 

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

Sign up to request clarification or add additional context in comments.

Comments

8

Remotely related, but maybe it helps someone: It took me some time to realize that errors.As(...) actually expects a double pointer to the target, while errors.Is(...) doesn't:

var _ error = (*CustomError)(nil) // ensure CustomError implements error type CustomError struct { msg string } func (e CustomError) Error() string { return e.msg } func main() { err := &CustomError{"Hello, world!"} // Methods return pointers to errors, allowing them to be nil var eval *CustomError as := errors.As(err, &eval) // yes, that's **CustomError asFaulty := errors.As(err, eval) // no compile error, so it wrongly seems okay is := errors.Is(err, eval) // that's just *CustomError fmt.Printf("as: %t, asFaulty: %t, is: %t", as, asFaulty, is) // as: true, asFaulty: false, is: true } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.