Go 1.13 错误包装需用 fmt.Errorf 配合 %w 显式声明,否则 errors.Is/As 失效;%w 仅接受单个 error 且须为最后参数,不可混用 %v;安全检查应优先用 errors.Is/As 而非字符串匹配;自定义 Unwrap() 适用于多错误聚合、附加上下文等场景。
Go 1.13 引入的
包错误包装机制,不是“加个字符串前缀”那么简单——它要求你用
配合
动词显式声明包装关系,否则
和
会失效。
为什么
不算包装?
这种写法只是把原始错误转成字符串再拼接,丢失了错误链结构。底层
不再可被
访问,
必然返回
,即使原始错误确实是
。
正确做法是:
关键点:
立即学习
“
go语言免费学习笔记(深入)
”;
只接受一个
类型参数,且必须是最后一个参数
不能混用
和
处理同一个错误值(比如
是错的)
如果要包装多个错误,需嵌套调用或用自定义错误类型实现
如何安全地检查和提取被包装的错误?
和
会自动沿
链向上遍历,但前提是每层都用了
或实现了标准接口。常见误用:
用
替代
—— 无法匹配包装后的错误
对未包装的错误调用
失败后不检查是否是原始类型,直接 panic
在中间层错误中漏掉
,导致下游无法追溯到根本原因
推荐检查模式:
什么时候该自己实现
而非依赖
?
当需要包装多个错误、携带额外上下文(如时间戳、请求 ID)、或控制展开逻辑时,
不够用。例如:
一个 RPC 调用失败,你想同时保留客户端错误、服务端返回的 status code 和 trace ID
数据库事务中多个操作出错,需聚合所有子错误而非只留最后一个
想限制展开深度(避免无限递归),或按条件跳过某些中间错误
简单自定义包装示例:
真正难的不是包装,而是团队里有人忘了加
,或者在日志里只打
而丢掉了整个错误链——这些地方一漏,排查时就只剩“某个地方错了”,没有上下文,也没有可编程判断的依据。
errorsfmt.Errorf%werrors.Iserrors.Asfmt.Errorf("failed: %v", err)errerrors.Unwrap()errors.Is(err, io.EOF)falseio.EOFerr := doSomething()
if err != nil {
return fmt.Errorf("processing item %s failed: %w", itemID, err) // ✅ 使用 %w
}%werror%w%vfmt.Errorf("%w: %v", err, err)Unwrap() errorerrors.Iserrors.AsUnwrap()%wstrings.Contains(err.Error(), "timeout")errors.Is(err, context.DeadlineExceeded)errors.As(err, &target)%wif errors.Is(err, os.ErrNotExist) {
// 处理文件不存在
} else if errors.Is(err, context.DeadlineExceeded) {
// 处理超时
} else if errors.As(err, &myCustomErr) {
// 提取自定义错误字段
}Unwrap()%w%wtype WrappedError struct {
Msg string
Cause error
TraceID string
}
func (e *WrappedError) Error() string {
return fmt.Sprintf("[%s] %s", e.TraceID, e.Msg)
}
func (e *WrappedError) Unwrap() error {
return e.Cause // ⚠️ 返回单个 error;若要多错误,需返回 []error 并配合自定义 Is/As%werr.Error()