不一致的错误:换行/错误 - 展开/fmt.Errorf(%w)

Tom*_*gan 3 error-handling go

fmt.Errorf使用with%w和 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 :-(
Run Code Online (Sandbox Code Playgroud)

完整的例子可以在这里找到

我不确定这是否是有意为之,但这似乎不一致......有什么原因吗?这有记录在任何地方吗?

icz*_*cza 6

这是预期的工作,并且不违反文档。多个错误可能会包装在一个error值中,并且由于调用Unwrap()返回一个错误,显然没有得到您期望的结果并不意味着预期的错误没有被包装。

errors包来自标准库。它没有errors.Wrap()功能。您使用的来自github.com/pkg/errors.Wrap(),它在引擎盖下进行“双重包装”。

首先,它用给定的错误消息包装错误,然后再次包装以保留堆栈信息:

// 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(),
    }
}
Run Code Online (Sandbox Code Playgroud)

当您调用 时Unwrap(),将返回第二次包装的错误(这不是原始错误,而是包装原始错误的包装错误),Unwrap()再次调用将返回原始错误。

fmt.Println("Double unwrap:",
    errors.Unwrap(errors.Unwrap(err2wrp)) == err1)
Run Code Online (Sandbox Code Playgroud)

这就是为什么你应该使用errors.Is()以避免此类“怪癖”:

fmt.Println("Proper use:", errors.Is(err2wrp, err1))
Run Code Online (Sandbox Code Playgroud)

在Go Playground上尝试这些。

请注意,上面的报告true是您调用github.com/pkg/errors.Is()还是标准库的errors.Is().