自定义错误需用结构体实现Error()和Unwrap()方法、导出字段(如Code)、包装用%w、提取用errors.As(&target)。errors.Is用于哨兵错误判断,errors.As用于提取结构体错误实例。

为什么 errors.New 和 fmt.Errorf 不够用
它们返回的都是 *errors.errorString 类型,无法携带额外上下文(比如错误码、请求ID、重试次数),也没法做类型断言区分错误种类。一旦业务逻辑需要「这个错误能不能重试」「是不是权限问题」,光靠错误文本匹配就不可靠又脆弱。
用结构体实现自定义错误类型的关键写法
必须实现 Error() 方法,返回字符串;推荐同时实现 Unwrap()(支持 Go 1.13+ 错误链);字段建议导出以便外部读取(如 Code、Meta)。
- 不要忘记导出字段,否则调用方无法访问
err.Code - 避免在
Error()中拼接敏感信息(如数据库密码),只用于日志或调试时展示 - 如果要嵌套底层错误,用
Unwrap() error返回它,别塞进字符串里
type AppError struct {
Code int
Message string
RequestID string
Err error // 底层原始错误
}
func (e *AppError) Error() string {
return fmt.Sprintf("[%d] %s (req: %s)", e.Code, e.Message, e.RequestID)
}
func (e *AppError) Unwrap() error {
return e.Err
}
如何判断和提取自定义错误(errors.As vs 类型断言)
errors.As 是唯一安全的方式——它能穿透多层错误包装(比如 fmt.Errorf("failed: %w", appErr)),而直接类型断言 err.(*AppError) 在有中间包装时必然失败。
- 永远优先用
errors.As(err, &target),不是err.(*AppError) - 目标变量必须是指针(
&target),且类型要和结构体一致 - 检查返回值是否为
true,再使用target
var appErr *AppError
if errors.As(err, &appErr) {
log.Printf("App error %d: %s", appErr.Code, appErr.Message)
if appErr.Code == 403 {
// 处理权限错误
}
}
什么时候该用 errors.Is,什么时候用 errors.As
errors.Is 适合判断「是不是某个预定义错误值」(比如 io.EOF 或你自己定义的 ErrNotFound = errors.New("not found"));errors.As 才是用来提取带字段的结构体错误实例的。
立即学习“go语言免费学习笔记(深入)”;
-
errors.Is(err, ErrNotFound)✅ 判断是否等于某个哨兵错误 -
errors.As(err, &appErr)✅ 提取结构体并读取appErr.Code -
errors.Is(err, &appErr)❌ 语法错,Is第二个参数必须是错误值,不是指针类型
%w,所有提取都得用 errors.As,而且结构体字段得导出——这三个点缺一不可,否则错误上下文就断了。










