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(...)。你们怎么看?

4 回复

感谢分享,我也有同样的想法。

更多关于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 的链中查找 err2
  • errors.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 通常是一个简单的哨兵错误。这种不对称性在这种使用场景下是合理且有用的。

回到顶部