errors.Is用于判断错误链中是否存在指定哨兵错误值,仅支持值相等比较,要求目标为包级变量;需自定义Is方法实现语义判断,不可替代os.Is*系列函数,且不能与errors.As混用。

errors.Is 不是用来“写函数”的工具,而是用来判断错误链中是否存在某个已知错误值的函数。它本身不接受自定义逻辑,也不支持通配或模糊匹配——你不能靠它实现“任意超时错误都返回 true”这种需求。
errors.Is 只能判断是否等于某个具体 error 值
它走的是「值相等」路径,不是类型检查,也不是字符串匹配:
- 目标必须是导出的包级变量,比如
io.EOF、os.ErrNotExist、sql.ErrNoRows - 不能传
errors.New("timeout")这种每次新建的 error——每次调用都生成新地址,errors.Is(err, errors.New("timeout"))永远 false - 也不能传类型,比如
errors.Is(err, &os.PathError{}),这既编译不过,也违背设计意图 - 如果你自己定义了哨兵错误,必须是包级变量:
var ErrTimeout = errors.New("timeout"),再用errors.Is(err, ErrTimeout)
想做语义判断?得在自定义错误里实现 Is 方法
标准库里的 os.PathError 和 net.OpError 都实现了 Is(target error) bool,所以 errors.Is(err, syscall.ECONNREFUSED) 能穿透到 Err 字段里比对。你自己写的错误类型如果也要支持这种行为,必须显式实现:
type MyTimeoutError struct {
Msg string
Code int
Cause error
}
<p>func (e <em>MyTimeoutError) Error() string { return e.Msg }
func (e </em>MyTimeoutError) Unwrap() error { return e.Cause }
func (e <em>MyTimeoutError) Is(target error) bool {
if target == context.DeadlineExceeded || target == context.Canceled {
return true
}
// 或者更细粒度:只对特定 code 判定
if te, ok := target.(</em>MyTimeoutError); ok && te.Code == e.Code {
return true
}
return false
}没实现 Is 方法时,errors.Is 只会做最基础的 == 比较,不会自动展开字段或逻辑推导。
立即学习“go语言免费学习笔记(深入)”;
别用 errors.Is 替代 os.Is* 系列函数
像 os.IsNotExist、os.IsPermission 这些函数,内部不只是调 errors.Is(err, os.ErrNotExist),还额外兼容了 syscall 层错误(如 syscall.ENOENT)。直接用 errors.Is(err, os.ErrNotExist) 在某些底层系统调用场景下会漏判:
-
os.Open返回的错误通常能被errors.Is正确识别 - 但通过
syscall.Open或某些 cgo 封装的库返回的错误,可能只含&os.PathError{Err: syscall.ENOENT},这时errors.Is(err, os.ErrNotExist)返回 false,而os.IsNotExist(err)仍为 true - 结论:有现成
os.Is*就用它,不要自己造轮子
errors.Is 和 errors.As 绝对不能混用场景
这是最容易踩的坑:以为 errors.Is(err, *os.PathError) 能判断类型——它根本不会编译;或者以为 errors.As(err, &io.EOF) 能提取值——io.EOF 是接口值,不是可寻址的指针类型,会 panic。
-
errors.Is的第二个参数必须是error类型的值(通常是变量),且该值需可比较 -
errors.As的第二个参数必须是*T类型(T 实现了error接口),且必须是指针,不能是值或接口 - 两者遍历错误链的方式一致,但目的完全不同:一个找“是不是这个错”,一个找“能不能转成这个类型”
真正容易被忽略的是:一旦错误链里出现一个没实现 Unwrap() 的中间错误(比如某层用了 errors.New 而非 fmt.Errorf("%w", ...)),整个链就断了——errors.Is 和 errors.As 都看不到更深层的内容,它们不会跳过、也不会 fallback。


















