Golang中`errors.Is(err1, err2)`的语义让你困惑吗?
Golang中errors.Is(err1, err2)的语义让你困惑吗?
大家好,关于 errors 有个小问题——有没有人感到困惑,errors.Is(e1,e2) 可能返回 true,而同时 errors.Is(e2,e1) 却可能返回 false?我团队里的一些人对此感到困惑,我想知道这是否是一个更普遍的问题。在我看来,errors.Is(..) 更像是 errors.ContainsInChain(...)。你们怎么看?
感谢分享,我也有同样的想法。
更多关于Golang中`errors.Is(err1, err2)`的语义让你困惑吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好,
错误之间的“Is”关系是不可交换的。这就像说“每个Go程序员都是人”,但这并不意味着每个人都是Go程序员。
你说得对。“Is” 用于判断 err 链中是否有任何错误与 target 匹配。可能你的同事在想的是 err1 == err2…
errors.Is(err1, err2) 的不对称性确实是 Go 错误处理中一个需要明确理解的设计。这不是 bug,而是有意为之的设计选择。
核心语义解析:
errors.Is(err1, err2) 检查的是 err1 的错误链中是否包含与 err2 匹配的错误。这里的 “包含” 是关键——它遍历 err1 的错误链(通过 Unwrap() 方法),检查链中是否有任何错误与 err2 相等(使用 == 比较或通过 Is() 方法判断)。
不对称性的原因:
errors.Is(err1, err2):在err1的链中查找err2errors.Is(err2, err1):在err2的链中查找err1
如果 err1 包装了 err2,那么 err1 的链中包含 err2,但 err2 的链中不包含 err1。
示例代码说明:
package main
import (
"errors"
"fmt"
)
func main() {
// 基础错误
baseErr := errors.New("base error")
// 包装基础错误
wrappedErr := fmt.Errorf("operation failed: %w", baseErr)
// 测试不对称性
fmt.Println("errors.Is(wrappedErr, baseErr):", errors.Is(wrappedErr, baseErr)) // true
fmt.Println("errors.Is(baseErr, wrappedErr):", errors.Is(baseErr, wrappedErr)) // false
// 自定义错误类型示例
type MyError struct {
Code int
Err error
}
func (e *MyError) Error() string {
return fmt.Sprintf("code %d: %v", e.Code, e.Err)
}
func (e *MyError) Unwrap() error {
return e.Err
}
func (e *MyError) Is(target error) bool {
if me, ok := target.(*MyError); ok {
return e.Code == me.Code
}
return false
}
err1 := &MyError{Code: 404, Err: baseErr}
err2 := &MyError{Code: 404, Err: nil}
fmt.Println("errors.Is(err1, err2):", errors.Is(err1, err2)) // true - 通过 Is() 方法匹配
fmt.Println("errors.Is(err2, err1):", errors.Is(err2, err1)) // false - err2 没有错误链
}
你的观察是正确的:
errors.Is(err1, err2) 确实可以理解为 “err1 的错误链中是否包含 err2”。这种设计允许错误包装和错误类型匹配的灵活性,但需要开发者理解其单向遍历的特性。
在实际使用中,通常的模式是:
if errors.Is(err, ErrNotFound) {
// 处理未找到错误
}
这里 err 可能是包装了 ErrNotFound 的复杂错误,而 ErrNotFound 通常是一个简单的哨兵错误。这种不对称性在这种使用场景下是合理且有用的。

