go 1.13+ Errors 包核心用于错误链与动态判断,非替代 fmt.errorf;必须用 errors.is 而非 == 判断多层包装错误(如 io.EOF);errors.as 用于提取底层结构化错误值;%w 是唯一推荐的包装方式,需保留上下文并避免字符串拼接。

Go 1.13+ 的 errors 包不是用来“替代”fmt.Errorf的,而是为错误链(error wrapping)和动态判断提供标准能力——不用它也能写代码,但一旦涉及错误分类、日志溯源或用户提示分级,绕不开它。
什么时候必须用 errors.Is 而不是 ==
当错误来自第三方库、中间件或跨 goroutine 传递时,原始错误常被多层包装(比如 fmt.Errorf("failed: %w", err)),此时直接比较指针或值会失败。
-
errors.Is(err, io.EOF)可穿透任意层数的%w包装,安全判断是否最终是 EOF -
err == io.EOF几乎总返回false,因为包装后已不是同一个实例 - 自定义错误类型也适用:只要实现了
Unwrap() error方法,errors.Is就能递归检查
errors.As 用于提取底层错误值的场景
你需要访问被包装错误的具体字段或方法(比如 http 状态码、数据库错误码),而不是只做布尔判断。
- 常见于处理
*url.Error、*os.PathError、sqlite3.Error等带结构体信息的错误 - 写法:
var urlErr *url.Error; if errors.As(err, &urlErr) { log.Println(urlErr.URL) } - 注意:第二个参数必须是指向具体类型的指针,且该类型需实现
Unwrap()或本身就是包装器 - 如果错误链里有多个同类型实例,
errors.As只返回最内层匹配的那个
fmt.Errorf 的 %w 动词是唯一推荐的错误包装方式
别再用 fmt.Errorf("xxx: %v", err) 或字符串拼接——这会丢失原始错误类型和堆栈上下文。
立即学习“go语言免费学习笔记(深入)”;
-
%w触发 Go 的隐式包装机制,让errors.Is/errors.As生效 - 只能在
fmt.Errorf中使用一次,且只能跟一个error类型参数 - 错误消息里不要省略关键上下文:比如
fmt.Errorf("reading config file %q: %w", path, err),而非"%w" - 若需多错误聚合(如多个 goroutine 失败),用
errors.Join,但要注意它不支持errors.As提取单个子错误
调试时快速展开错误链的实用技巧
生产环境日志里看到一串“wrapped by”却看不出源头?errors.Unwrap 和 %+v 是你的基础工具。
-
fmt.printf("%+vn", err)会打印完整错误链 + 每一层的调用栈(需启用-gcflags="all=-l"避免内联丢失) -
errors.Unwrap(err)返回直接被包装的错误,可手动循环展开(但一般用errors.Is/errors.As更安全) - 自定义错误类型中,若需添加额外字段(如 trace ID),务必同时实现
Unwrap() error和Error() String,否则会被errors包忽略
真正容易被忽略的是:errors.Is 和 errors.As 对 nil 错误返回 false,但很多人忘了先判空;另外,任何中间层用了非 %w 的包装(比如 sprintf),整条链就断了——这种问题在线上很难复现,只能靠 Code Review 和单元测试覆盖关键路径。