Home > front end >  Inconsistent Errors: Wrap / Errors - Unwrap / fmt.Errorf(%w)
Inconsistent Errors: Wrap / Errors - Unwrap / fmt.Errorf(%w)

Time:12-27

There seems to be an inconsistency between wrapping an error using fmt.Errorf with %w and using errors.Wrap:

    e1 := errors.New("error1")
    efmt := fmt.Errorf("error2: %w", e1)
    eerr := errors.Wrap(e1, "error2")

    fmt.Println(errors.Unwrap(efmt))       // error1
    fmt.Println(errors.Unwrap(efmt) == e1) // true
    fmt.Println(errors.Unwrap(eerr))       // error2: error1
    fmt.Println(errors.Unwrap(eerr) == e1) // false :-(

A full example is available here

I am not sure whether that is intended, but this seem inconsistent... Is there a reason for that? Is this documented anywhere?

CodePudding user response:

This is the intended working, and this does not violate the docs. Multiple errors may be wrapped in a single error value, and since a call to Unwrap() returns a single error, obviously not getting what you expect doesn't mean the expected error is not wrapped.

The errors package is from the standard lib. It does not have an errors.Wrap() function. The one you're using is from github.com/pkg/errors.Wrap(), which does "double-wrapping" under the hood.

func Wrap(err error, message string) error

First it wraps the error with the given error message, then it wraps again to retain the stack information:

// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
    if err == nil {
        return nil
    }
    err = &withMessage{
        cause: err,
        msg:   message,
    }
    return &withStack{
        err,
        callers(),
    }
}

When you call Unwrap(), then the error from second wrapping will be returned (which is not the original error, but a wrapped error wrapping the original), calling Unwrap() again would return the original error.

fmt.Println("Double unwrap:",
    errors.Unwrap(errors.Unwrap(err2wrp)) == err1)

That's why you should use errors.Is() to avoid such "quirks":

fmt.Println("Proper use:", errors.Is(err2wrp, err1))

Try these on the Go Playground.

Note that the above reports true whether you call github.com/pkg/errors.Is() or the standard lib's errors.Is().

  • Related