errors.is用于判断错误链中是否存在特定哨兵错误值,而非类型;判断类型应使用errors.as;常见误用包括错误链断裂、临时构造错误、混淆语义等价与值相等。

errors.Is 不是用来判断“错误类型”的,它是用来判断错误链中是否存在某个特定错误值(哨兵错误)的。想查类型,得用 errors.As。
errors.Is(err, io.EOF) 为什么有时返回 false
根本不是函数有问题,而是错误链断了或目标传错了。常见原因有三个:
- 中间某层用了
fmt.Errorf("xxx: %v", err)而不是%w,导致Unwrap()返回nil,遍历提前终止 - 你自己构造了新错误:比如写成
errors.Is(err, errors.New("not found"))—— 每次调用都生成新地址,==必然失败 - 错误来自 syscall 层(如
&os.PathError{Err: syscall.ENOENT}),它不包含os.ErrNotExist这个变量,只是语义等价;此时应改用os.IsNotExist(err),它内部做了额外兼容
errors.Is 的目标参数只能是变量,不能是类型或字面量
它比的是值相等(==),不是类型匹配。所以这些写法全错:
-
errors.Is(err, *os.PathError{})—— 编译不过,且每次新建结构体地址不同 -
errors.Is(err, (*os.PathError)(nil))——nil指针无法参与==判断 -
errors.Is(err, fmt.Errorf("timeout"))—— 临时构造,无复用性
正确做法是定义包级变量:var ErrTimeout = errors.New("timeout"),然后用 errors.Is(err, ErrTimeout)。
自定义错误要支持 errors.Is,必须实现 Is 方法
只靠 Unwrap() 不够。如果希望 errors.Is(err, MyErr) 能穿透包装并命中,你的错误类型得显式实现 Is(target error) bool 方法。例如:
type ValidationError struct {
Msg string
Inner error
}
func (e *ValidationError) Error() string { return e.Msg }
func (e *ValidationError) Unwrap() error { return e.Inner }
func (e *ValidationError) Is(target error) bool {
if target == ErrValidationFailed {
return true
}
return errors.Is(e.Inner, target)
}
否则 errors.Is 只会拿 *ValidationError 和 target 做 ==,基本不可能成立。
errors.Is 和 errors.As 绝对不能混用场景
这是最常被忽略的边界:
- 你想知道“是不是文件不存在” → 用
errors.Is(err, os.ErrNotExist) - 你想读出“在哪个路径上失败” → 用
errors.As(err, &pathErr)提取*os.PathError,再访问pathErr.Path - 你想同时做两件事?先
Is快速分流,再As提取上下文 —— 但别反过来,也别用Is去匹配类型
真正容易踩坑的地方在于:很多人以为 errors.Is(err, &os.PathError{}) 能判断类型,其实它既不合法(编译报错),也不达目的(Is 不处理类型)。这类误用一旦上线,在错误被包装后就会静默失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











