应使用 errors.as 而非手动类型断言,因其对 nil 安全且能递归遍历错误链;errors.is 用于匹配哨兵错误值,errors.as 用于提取错误类型;自定义错误须实现 unwrap 方法,避免循环引用。

用 errors.As 而不是手动类型断言
直接对 error 做 err.(MyError) 很容易 panic,尤其当 err 是 nil 或底层类型不匹配时。Go 1.13 引入的 errors.As 才是安全提取错误类型的正解——它会递归检查错误链(wrapped error),且对 nil 安全。
- 手动断言
err.(*os.PathError)在err是fmt.Errorf("wrap: %w", pathErr)时失败,而errors.As(err, &pathErr)成功 - 必须传入指针:第二个参数是
*T类型,不是T - 返回
bool表示是否找到匹配项,别忽略返回值
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Println("路径错误:", pathErr.Path)
}
什么时候该用 errors.Is 而不是 errors.As
errors.Is 判断的是“是否等于某个已知错误值”,比如 os.ErrNotExist;errors.As 判断的是“是否能转成某类错误”。两者解决的问题完全不同,混用会导致逻辑错乱。
-
errors.Is(err, os.ErrNotExist)→ 检查是不是“文件不存在”这个具体值(或其包装) -
errors.As(err, &pathErr)→ 检查底层有没有*os.PathError实例,不管它是不是os.ErrNotExist - 常见误用:想判断是否是网络超时,却写
errors.Is(err, context.DeadlineExceeded)—— 这只对裸错误有效;实际中多数是net/http或database/sql包装过的,得用errors.As提取*net.OpError再看.Timeout()
自定义错误类型要实现 Unwrap 才能被正确遍历
如果你自己封装错误并希望 errors.As 和 errors.Is 能穿透它,必须显式实现 Unwrap() error 方法。否则错误链在你这层就断了。
- 没实现
Unwrap:外层调用errors.As(wrappedErr, &inner)返回 false - 正确做法:返回内部错误,且注意 nil 安全(如果 innerErr 是 nil,
Unwrap应返回 nil) - 不要在
Unwrap里做日志、格式化或额外判断——它只负责“向下透出”
type MyError struct {
msg string
cause error
}
func (e *MyError) Error() string { return e.msg }
func (e *MyError) Unwrap() error { return e.cause }
嵌套过深或循环引用会让 errors.As 变慢甚至卡住
errors.As 默认最多遍历 10 层错误链,超过就停止。但如果你的错误链人为构造出循环(比如 A 包装 B,B 又包装 A),它会无限循环直到栈溢出。
立即学习“go语言免费学习笔记(深入)”;
- 典型场景:中间件或日志库在 defer 中反复包装同一错误
- 没有运行时检测循环引用,只能靠代码审查和测试覆盖
- 性能敏感路径(如高频 RPC 错误处理)建议先用
errors.Is快速匹配已知哨兵值,再按需用errors.As提取细节
最麻烦的不是写错,是忘了自定义错误要实现 Unwrap,或者在包装时无意引入循环——这两处一漏,下游所有 errors.As 都会静默失效。










