errors.Is不能直接比对fmt.Errorf("xxx"),因为它只匹配错误链中已知的错误值(如io.EOF、os.ErrNotExist或包级变量),不识别临时构造的错误。
errors.Is 为什么不能直接比对 fmt.Errorf("xxx")?
因为 errors.is 只匹配错误链中「已知的错误值」,比如标准库导出的 io.eof、os.errnotexist,或你自己定义的变量 var errtimeout = errors.new("timeout")。它不认临时构造的错误:errors.is(err, fmt.errorf("not found")) 永远返回 false——哪怕底层真是这个字符串,也不在错误链里。
常见错误现象:日志里看到 "file not found",就写 errors.Is(err, errors.New("file not found")),结果判断失效。
- 必须用变量或标准库常量,不能现场
errors.New或fmt.Errorf - 如果要用自定义字符串做判断,得提前定义为包级变量,并确保所有包装都用
%w -
errors.Is(err, nil)总是false,空错误仍得用err == nil
errors.As 传参必须是指针,且类型要具体
errors.As 的作用是从错误链里提取第一个能成功赋值的「具体类型」实例,比如 *os.PathError、*sqlite.Error。它不是类型断言的语法糖,而是递归遍历整个链,直到找到匹配项。
常见错误现象:编译报错 cannot use pathErr (type *os.PathError) as type interface{},或者运行时 panic,原因是目标变量没传地址,或类型不对。
- 必须传指向目标类型的指针:
&pathErr,不能传pathErr(值)或pathErr(接口) - 只支持具体类型,不支持接口类型(除非该接口自己实现了
As()方法) - 匹配失败时,目标变量保持原值(不会被置零),所以一定要检查
errors.As返回的bool
错误链断裂的典型场景:%w 用错了
errors.Is 和 errors.As 能穿透多层包装,前提是每层都用 fmt.Errorf("msg: %w", err),且 %w 是唯一且末尾的动词。任何偏离都会让错误链在某一层断掉。
常见错误现象:errors.Is(err, io.EOF) 返回 false,但打印 err.Error() 明明包含 "EOF";或者 errors.As(err, &pathErr) 总是失败,但 err 确实来自 os.Open。
- 用了
%v、%s或字符串拼接:fmt.Errorf("read failed: %v", err)→ 链断裂 -
%w不在末尾:fmt.Errorf("wrap: %w: extra", err)→ 编译失败或行为未定义 - 包装了没有
Unwrap()方法的错误(如裸errors.New)→ 后续层无法继续展开
自定义错误类型必须实现 Unwrap() 才能参与链式判断
如果你写了结构体错误并希望它能被 errors.Is 和 errors.As 正确识别,光有 Error() 不够。它必须显式实现 Unwrap() error 方法,返回被包裹的错误;否则它就是链的终点,再往里查不到东西。
常见错误现象:自定义错误包装了 os.PathError,但 errors.As(err, &pathErr) 失败;或者 errors.Is(err, os.ErrNotExist) 不生效。
-
Unwrap()必须返回实际包裹的错误,哪怕是nil;返回nil表示“到此为止”,不是“没包裹” - 若还希望支持
Is()和As()向下委托,可以额外实现Is(target error) bool和As(target any) bool方法 - 不要在
Unwrap()里做逻辑判断或懒加载,它应是纯数据访问
最易被忽略的一点:错误链不是自动形成的,而是靠每个环节主动选择是否用 %w 和是否实现 Unwrap()。一旦中间某层断开,上层的 errors.Is 和 errors.As 就只能看到断点之后的部分。











