Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error wrap/unwrap && type checking with errors.Is()

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().

like image 455
errolflynn Avatar asked Apr 17 '26 02:04

errolflynn


2 Answers

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

like image 147
colm.anseo Avatar answered Apr 20 '26 13:04

colm.anseo


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
}
like image 25
NotX Avatar answered Apr 20 '26 14:04

NotX



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!