errors.Join 是 Go 1.20 引入的用于合并多个 error 值的函数,支持 errors.Is/As 检查、自动过滤 nil、扁平化一层嵌套,但不支持字符串拼接或递归展开。

errors.Join 是什么,为什么不能用 + 或 fmt.Errorf 拼接错误
Go 1.20 引入 errors.Join,专为合并多个错误设计。它不是字符串拼接,而是把多个 error 值组合成一个可遍历、可判断、可展开的复合错误。直接用 + 会报错(invalid operation: + (mismatched types error and error)),而 fmt.Errorf("%w %w", err1, err2) 只能包两层,且丢失结构——无法用 errors.Is 或 errors.As 同时匹配多个底层错误。
怎么用 errors.Join:参数顺序、nil 处理和嵌套限制
errors.Join 接收任意数量的 error 参数,返回一个新错误。它会自动过滤掉 nil 值,所以传 nil 安全;但要注意:如果所有参数都是 nil,返回的是 nil,不是空错误。
- 参数顺序影响
errors.Unwrap的展开顺序(从左到右) - 不支持递归展开嵌套的
Join错误——它只扁平化一层,不会把子Join再拆开 - 避免传入已用
fmt.Errorf("%w")包裹过的错误链顶端,否则可能重复包裹
示例:
err := errors.Join(io.ErrUnexpectedEOF, os.ErrPermission, nil) // 返回一个 error,errors.Is(err, io.ErrUnexpectedEOF) == true // errors.Is(err, os.ErrPermission) == true
errors.Join 和 errors.Is / errors.As 怎么配合用
errors.Join 返回的错误实现了 Unwrap 方法,返回所有非 nil 参数组成的切片。因此 errors.Is 会逐个检查每个成员是否匹配目标错误;errors.As 也会依次尝试转换每个成员。
- 只要任一成员满足
errors.Is条件,整个Join错误就返回true -
errors.As成功后,拿到的是第一个匹配的成员值,不是Join本身 - 注意:如果多个成员都实现了同一接口,
As只返回第一个匹配项,不会聚合
常见误用:
joined := errors.Join(sql.ErrNoRows, fs.ErrNotExist)
var e *fs.PathError
if errors.As(joined, &e) { /* 不会进这里,因为 sql.ErrNoRows 不是 *fs.PathError */ }
哪些场景适合用,哪些该避免
适合批量操作失败时汇总错误,比如并发写多个文件、批量数据库插入、校验多个字段等。不适合替代单层错误包装或做日志格式化。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 适合:
for range中收集多个 goroutine 的错误,最后errors.Join合并 - ✅ 适合:HTTP handler 中同时验证 token、权限、参数,任一失败都记录,最终统一返回
- ❌ 避免:只有一两个错误时硬套
Join,不如直接fmt.Errorf("x: %w, y: %w", a, b) - ❌ 避免:在中间件或包装器里无差别
Join上游错误和本地错误,容易污染错误树
真正难处理的是错误来源混杂(比如有的带堆栈、有的不带),errors.Join 不处理堆栈,也不标准化消息——这些得靠上层日志或错误构造器补足。










