Go 1.13 引入错误包装与解包机制,通过 fmt.Errorf 的 %w 动词嵌套错误形成链式结构,配合 errors.Unwrap(单层解包)、errors.Is/As(自动遍历链式匹配或类型提取)实现清晰可追溯的错误处理。

Go 1.13 引入了错误包装(error wrapping)和解包(unwrapping)机制,让错误处理更清晰、可追溯。核心在于用 fmt.Errorf 配合 %w 动词包装错误,并用 errors.Unwrap 或 errors.Is/errors.As 向下查找原始错误。
用 %w 包装错误,保留原始上下文
包装错误不是简单拼接字符串,而是将底层错误“嵌套”进去,形成链式结构:
说明: %w 会把传入的 error 值作为内部错误保存,调用 Unwrap() 方法即可获取它。
建议:
立即学习“go语言免费学习笔记(深入)”;
- 只在需要添加上下文(如函数名、参数、阶段标识)时包装,避免无意义层层包裹
- 不要对
nil错误使用%w,否则fmt.Errorf("xxx: %w", nil)会返回nil错误 - 示例:
func readFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file %q failed: %w", path, err) // 包装
}
// ...
return nil
}
用 errors.Unwrap 获取直接被包装的错误
errors.Unwrap 返回错误的直接下一层(即被 %w 包裹的那个),如果该错误不支持 Unwrap() error 方法或返回 nil,则结果为 nil。
说明: 它只解一层,不是递归解包。适合做单步检查或手动遍历错误链。
建议:
立即学习“go语言免费学习笔记(深入)”;
- 不要依赖多次调用
Unwrap手动“剥洋葱”,应优先用errors.Is或errors.As - 可配合循环实现自定义解包(但一般不需要):
for err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file missing: %w", err)
}
err = errors.Unwrap(err) // 仅解一层
}
推荐方式:用 errors.Is 和 errors.As 判断和提取原始错误
比起手动 Unwrap,errors.Is 会自动沿整个错误链向上匹配目标错误(比如 os.ErrNotExist),errors.As 则用于提取特定类型的错误值。
说明: 它们内部已处理多层包装,语义清晰、安全可靠。
建议:
立即学习“go语言免费学习笔记(深入)”;
- 判断是否是某类错误(如超时、不存在、权限拒绝)→ 用
errors.Is(err, target) - 需要访问错误的具体字段或方法(如
*os.PathError的Path字段)→ 用errors.As(err, &target) - 示例:
if errors.Is(err, os.ErrNotExist) {
log.Println("file does not exist")
}
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("failed on path: %s", pathErr.Path)
}
注意:自定义错误类型需实现 Unwrap 方法才能被正确解包
如果你定义了自己的错误类型并希望它能参与标准解包流程,必须显式实现 Unwrap() error 方法。
说明: 只有实现了该方法的错误,才会被 errors.Unwrap、Is、As 等识别为可包装错误。
建议:
立即学习“go语言免费学习笔记(深入)”;
- 若错误包含一个底层 error 字段,直接返回它即可
- 若不包装其他错误,返回
nil - 示例:
type MyError struct {
msg string
cause error
}
func (e *MyError) Error() string { return e.msg }
func (e *MyError) Unwrap() error { return e.cause } // 关键:支持解包










