如何在Golang中自定义错误类型_Golang errors与自定义结构体用法

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

为什么 errors.Newfmt.Errorf 不够用

它们返回的都是 *errors.errorString 类型,无法携带额外上下文(比如错误码、请求ID、重试次数),也没法做类型断言区分错误种类。一旦业务逻辑需要「这个错误能不能重试」「是不是权限问题」,光靠错误文本匹配就不可靠又脆弱。

用结构体实现自定义错误类型的关键写法

必须实现 Error() 方法,返回字符串;推荐同时实现 Unwrap()(支持 Go 1.1

3+ 错误链);字段建议导出以便外部读取(如 CodeMeta)。

  • 不要忘记导出字段,否则调用方无法访问 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 才是用来提取带字段的结构体错误实例的。

  • errors.Is(err, ErrNotFound) ✅ 判断是否等于某个哨兵错误
  • errors.As(err, &appErr) ✅ 提取结构体并读取 appErr.Code
  • errors.Is(err, &appErr) ❌ 语法错,Is 第二个参数必须是错误值,不是指针类型
自定义错误结构体本身不难,真正容易漏掉的是:所有中间包装都得用 %w,所有提取都得用 errors.As,而且结构体字段得导出——这三个点缺一不可,否则错误上下文就断了。