errors.unwrap只解一层,需手动循环调用并判空防死循环;errors.is可递归匹配整个错误链,比手写unwrap更可靠,要求每层用%w包装且正确实现unwrap()方法。

Go 1.13+ 的 errors.Unwrap 和 errors.Is 怎么用才不漏掉嵌套错误
Go 的 error wrapping(比如用 fmt.Errorf("failed: %w", err))不是简单套一层,而是构建了可递归展开的错误链。直接用 == 或 strings.Contains(err.Error(), "...") 会跳过中间层,根本捕不到底层原因。
关键点在于:所有判断都得走 errors.Is,所有展开都得靠 errors.Unwrap,不能依赖 err.Error() 字符串匹配。
-
errors.Is(err, io.EOF)会顺着%w链一直查到最里层,哪怕中间 wrap 了 5 层也有效 -
errors.As(err, &target)同样递归查找匹配的类型,适合提取自定义错误结构体 - 手动递归展开时,别写
for err != nil—— 正确写法是for err != nil { ... err = errors.Unwrap(err) },否则可能无限循环(比如某个 wrapper 的Unwrap()返回自身)
自定义错误类型怎么实现 Unwrap() 才支持标准库递归处理
只要实现了 Unwrap() error 方法,你的错误类型就能被 errors.Is、errors.As 和 fmt.Printf("%+v", err) 自动识别并展开。但注意返回值语义:必须返回“直接原因”,不是任意包装或 nil。
常见错误是返回 nil 或返回无关 error,导致链断裂或误判。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 如果错误没有底层原因(比如纯逻辑错误),
Unwrap()应返回nil - 如果封装了另一个 error(比如重试包装器),
Unwrap()必须返回那个原始 error,不能返回新构造的fmt.Errorf(...) - 多个原因?Go 官方不支持多路 unwrap;要么选一个主因,要么用第三方包(如
pkg/errors的WithStack),但会失去标准库兼容性
用 fmt.Errorf("%w") 时哪些参数位置和类型容易踩坑
%w 只接受 error 类型参数,且必须是最后一个(或唯一一个)格式化动词。放错位置或混用其他动词,编译不报错但运行时行为异常。
- ❌ 错误:
fmt.Errorf("retry %d: %w failed", n, err)——%w后面还有文字,Go 会忽略%w,当作普通字符串处理 - ✅ 正确:
fmt.Errorf("retry %d failed: %w", n, err)——%w在末尾,且前面有明确上下文 - ❌ 类型错:
fmt.Errorf("wrapped: %w", "not an error")—— 编译失败,%w要求error接口,字符串不行 - ⚠️ 注意:
%w不会触发String()或Error()方法调用,它只取 error 值本身,所以不要指望它“格式化”子错误
调试时怎么看清 error 的完整包裹链
fmt.Printf("%+v", err) 是唯一能默认打印完整 wrapper 链的方式,前提是每个 wrapper 都正确实现了 Unwrap()。仅用 fmt.Println(err) 或 err.Error() 只显示最外层消息。
- 输出示例:
rpc timeout after 3s: context deadline exceeded—— 看似单行,但%+v会展开成带缩进的层级结构 - 想程序化遍历链?用
errors.Unwrap循环,配合fmt.Sprintf("%T", err)查看每层类型 - 日志中记录错误链?别只打
err.Error();用slog.With("err", err)(Go 1.21+)或第三方日志库支持%+v的格式化器
递归处理 error 的复杂点不在语法,而在每一层 Unwrap() 的语义是否一致、是否真代表因果关系。很多人在中间层返回了错误但没暴露原始 error,或者把非错误信息塞进 %w 占位,结果调试时链就断了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










