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().
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
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With