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 会把传入的 error 值作为内部错误保存,调用 Unwrap() 方法即可获取它。
建议:
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 返回错误的直接下一层(即被 % 包裹的那个),如果该错误不支持
wUnwrap() error 方法或返回 nil,则结果为 nil。
说明: 它只解一层,不是递归解包。适合做单步检查或手动遍历错误链。
建议:
Unwrap 手动“剥洋葱”,应优先用 errors.Is 或 errors.As
for err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file missing: %w", err)
}
err = errors.Unwrap(err) // 仅解一层
}
比起手动 Unwrap,errors.Is 会自动沿整个错误链向上匹配目标错误(比如 os.ErrNotExist),errors.As 则用于提取特定类型的错误值。
说明: 它们内部已处理多层包装,语义清晰、安全可靠。
建议:
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() error 方法。
说明: 只有实现了该方法的错误,才会被 errors.Unwrap、Is、As 等识别为可包装错误。
建议:
nil
type MyError struct {
msg string
cause error
}
func (e *MyError) Error() string { return e.msg }
func (e *MyError) Unwrap() error { return e.cause } // 关键:支持解包