Go 1.20+ 推荐用 errors.Join 合并多个错误,它按参数顺序拼接、忽略 nil、支持 errors.Is/As;需显式判空,fmt.Printf(“%+v”, err) 查看完整嵌套;自定义错误应实现 *T.Unwrap() error;multierr 更适合动态聚合;API 响应须结构化,勿直接暴露复合错误。
Go 1.20+ 使用 errors.Join 合并多个错误
当函数内部触发多个独立错误(比如并发操作中多个 goroutine 失败),又不想只返回第一个,errors.Join 是最直接的内置方案。它把多个 error 合成一个可展开的复合错误,且支持 errors.Is 和 errors.As。
常见错误是误以为 errors.Join 会“去重”或自动排序——它只是按参数顺序拼接,且不检查 nil;传入 nil 会被忽略,但不会报错,容易漏掉本应关注的错误。
- 必须显式检查每个子错误是否为 nil,再决定是否加入:
errors.Join(err1, err2, err3)中若err2 == nil,它不会出现在最终错误中 - 适合场景:批量 I/O、并行 HTTP 请求、多数据库写入等“尽力而为”型逻辑
- 注意:
errors.Join返回的错误在fmt.Println下默认只显示摘要,需用fmt.Printf("%+v", err)才能看到全部嵌套栈
自定义错误类型实现 Unwrap 支持链式错误
如果需要携带上下文(如操作名、ID、重试次数)或控制错误展示格式,不能只靠 errors.Join。此时应定义结构体并实现 Unwrap() error 方法,让错误可被标准库递归解析。
典型陷阱是忘记返回指针类型错误——Unwrap 签名要求返回 error 接口,若方法值接收者返回的是值拷贝,可能导致循环引用或无法正确解包。
- 推荐用指针接收者:
func (e *MultiOpError) Unwrap() error - 避免在
Unwrap中做复杂计算或阻塞操作,它可能被日志、监控等中间件高频调用 - 若需保留多个底层错误(不止一个),可返回
errors.Join结果作为Unwrap返回值,形成混合结构
使用 multierr 库替代手写聚合逻辑
multierr(由 Go 团队成员维护)比 errors.Join 更灵活:支持追加、过滤、延迟合并,并提供 multierr.Append 这类更符合直觉的 API。尤其适合错误收集发生在循环或条件分支中时。