登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go errors.As 目标参数为什么必须是非空指针

来源:17golang原创

时间:2026-09-27 21:37:28 458浏览 收藏

errors.As 的 target 必须是非空指针,因为它不只是检查类型,还要把错误树中找到的具体错误写进调用者提供的变量。最容易误解的一点是:目标变量本身可以是 nil,不能为 nil 的是传给 errors.As 的外层地址。

官方文档:https://pkg.go.dev/errors#As

要点速览
  • var target *MyError 初始为 nil 没问题,调用时应传 &target。
  • target 必须是非 nil 指针,且它指向的类型要实现 error,或者本身是接口类型。
  • errors.As 会检查整棵错误树,并把首个可赋值的匹配值写回目标变量。
  • 目标参数形状错误属于程序员错误,因此标准库选择 panic,而不是返回 false。

先用最小实验看懂双指针

我第一次写 errors.As 时也觉得 &target 多了一层。真正理解后就很简单:错误值通常是 *ValidationError,而目标变量也要保存这个指针;为了让函数修改目标变量,就必须再传目标变量自己的地址,于是参数类型自然变成 **ValidationError。

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "invalid field: " + e.Field
}

func validate() error {
    // 用 %w 保留底层错误,使 errors.As 能继续检查错误树
    return fmt.Errorf("save config: %w", &ValidationError{Field: "port"})
}

func main() {
    err := validate()
    var target *ValidationError // 目标值可以暂时是 nil

    // &target 是非 nil 的可写槽位,匹配值会写回 target
    if errors.As(err, &target) {
        fmt.Println(target.Field)
    }
}
errors.As 目标参数、非空指针、可写槽位和目标类型边界结构图
图1:errors.As 目标参数边界结构图,说明外层指针用于提供可写槽位,不是运行截图。

三个条件中任何一个不满足都会 panic

官方文档给出的约束可以拆成三项。第一,传入值必须是指针;第二,这个指针不能为 nil;第三,指针指向的类型必须实现 error,或者是任意接口类型。下面几种写法看似相近,运行意义完全不同:

var pointerTarget *ValidationError

// 错误:传入的是 nil 的 *ValidationError,不是目标变量的地址
// errors.As(err, pointerTarget)

var valueTarget ValidationError
// 错误:传入的是普通结构体,errors.As 没有位置写回结果
// errors.As(err, valueTarget)

// 正确:&pointerTarget 是 **ValidationError,提供了可写槽位
if errors.As(err, &pointerTarget) {
    fmt.Println(pointerTarget.Field)
}

这也是为什么参数错误会 panic:false 应只表示“错误树里没有匹配类型”,不能同时表示“调用方式本身无效”。把两类问题分开后,测试失败时更容易定位。

匹配发生在错误树,结果写进目标变量

errors.As 会先检查根错误,再深度优先检查由 Unwrap() error 或 Unwrap() []error 暴露的子节点。某个具体错误可赋值给目标指向的类型时就匹配成功;如果错误类型自定义了 As(any) bool,也可以参与匹配。找到首个匹配后,标准库把该值写入目标变量并返回 true。

errors.As 在根错误、包装错误和子错误中匹配目标类型的结构图
图2:errors.As 错误树匹配结构图,展示被检查节点与目标变量的类型关系。

接口目标也遵循同一规则,只是目标变量声明为接口。例如要识别可报告临时状态的错误,可以先定义最小接口,再传它的地址:

type temporary interface {
    Temporary() bool
}

var target temporary
// 指向接口变量的非 nil 指针同样可以接收匹配值
if errors.As(err, &target) && target.Temporary() {
    fmt.Println("retry later")
}

新版 Go 可以考虑 errors.AsType

在提供 errors.AsType 的 Go 版本中,大多数只想取得具体错误值的场景可以直接接收返回值,不再手动准备双指针。它仍然检查错误树和自定义 As 方法,但把类型参数和返回值放在调用表达式里,误传 nil target 的机会更少。

// 泛型参数直接表达要查找的错误类型,返回值就是匹配结果
if target, ok := errors.AsType[*ValidationError](err); ok {
    fmt.Println(target.Field)
}

如果项目需要兼容较早的 Go 工具链,继续使用 errors.As 即可。我的做法是把 var target *T 与 errors.As(err, &target) 紧挨着写,并在测试中覆盖直接错误、单层包装、多层包装和无匹配四种情况。

target 变量为 nil 为什么还能成功?

因为 nil 是 *ValidationError 的零值,而传入的 &target 是一个真实、非 nil 的地址,函数可以把匹配到的指针写进去。

能不能传 new(ValidationError)?

这只是 *ValidationError,而指针错误类型通常需要 **ValidationError 作为写回位置。最清楚的写法仍是先声明目标变量,再传它的地址。

errors.As 返回 false 后能使用 target 吗?

不应依赖它。把目标变量视为只有在返回 true 时才有效,可以避免复用旧变量时误读残留值。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>