不一致的错误:Wrap / Errors – Unwrap / fmt.Errorf(%w)

huangapple go评论89阅读模式
英文:

Inconsistent Errors: Wrap / Errors - Unwrap / fmt.Errorf(%w)

问题

使用fmt.Errorf%w包装错误与使用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 :-(

完整示例可在此处找到。

我不确定这是否是有意为之的,但这似乎是不一致的... 是否有原因解释这种情况?这是否有文档记录?

英文:

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?

答案1

得分: 6

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

errors包来自标准库。它没有errors.Wrap()函数。你正在使用的是github.com/pkg/errors.Wrap(),它在内部进行了“双重包装”。

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

// Wrap返回一个在调用Wrap时在err上注释了堆栈跟踪和提供的消息的错误。
// 如果err为nil,则Wrap返回nil。
func Wrap(err error, message string) error {
    if err == nil {
        return nil
    }
    err = &withMessage{
        cause: err,
        msg:   message,
    }
    return &withStack{
        err,
        callers(),
    }
}

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

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

这就是为什么你应该使用errors.Is()来避免这种“怪异行为”的原因:

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

Go Playground上尝试这些代码。

请注意,无论你调用github.com/pkg/errors.Is()还是标准库的errors.Is(),上述代码都会返回true

英文:

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.

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

huangapple
  • 本文由 发表于 2021年12月26日 23:30:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/70487660.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定