当前位置:首页 > 文章列表 > Golang > Go问答 > Go errors.As 为什么要求传入指针的指针

Go errors.As 为什么要求传入指针的指针

来源:17golang原创 2026-11-02 03:42:23 0浏览 收藏

很多人第一次写 errors.As 时,都会在这一行停住:明明想拿到一个 *MyError,为什么不能把它直接传进去,而要写成 &target?关键不在于 errors.As “偏爱指针的指针”,而在于它要把匹配到的错误值写回调用方的变量。

如果目标变量本身的类型是 *MyError,那么把这个变量的地址传给函数后,参数的动态类型自然就是 **MyError。第一颗指针让函数找到变量,第二颗指针只是目标变量本身的类型。理解这层关系,就能同时看懂正确调用、panic 和返回 false。

包装错误、目标类型、MyError 指针和 errors.As 写回位置的静态关系说明图
图1:errors.As 的 target 类型关系说明图,目标变量是 *MyError 时,传入 &target 才能让函数写回匹配到的指针。

先看清 errors.As 的 target 到底是什么

errors.As 的签名是 func As(err error, target any) bool。第一个参数是要检查的错误,第二个参数不是“类型名称”,而是一个可以被函数修改的目标位置。函数会在错误树中查找可赋值给目标类型的错误;找到后,把那个错误值写入 target 指向的位置,并返回 true。

下面的例子先用 %w 保留错误包装,再把最终的 *MyError 交给调用方:

package main

import (
    "errors"
    "fmt"
)

// MyError 保存调用方真正关心的结构化错误信息。
type MyError struct {
    Code int
    Msg  string
}

// Error 让 MyError 满足 error 接口。
func (e *MyError) Error() string {
    return fmt.Sprintf("code=%d: %s", e.Code, e.Msg)
}

func loadConfig() error {
    // %w 保留底层错误,errors.As 才能继续沿错误树查找。
    return fmt.Errorf("读取配置失败: %w", &MyError{Code: 1001, Msg: "字段缺失"})
}

func main() {
    err := loadConfig()

    var target *MyError
    // target 是接收结果的变量;&target 才是它的写入位置。
    if errors.As(err, &target) {
        fmt.Println(target.Code, target.Msg)
    }
}

这里的关系可以拆成三层:错误树里实际存在的是一个 *MyError;调用方声明的变量 target 也是 *MyError;为了让函数修改这个变量,调用时把它的地址 &target 传进去,于是 target 参数接收到的是 **MyError。

为什么 *MyError 要写成 **MyError

把上面的变量换成类型符号更直观。设匹配到的错误类型为 E:

  • 如果 E 是 *MyError,接收结果的变量就是 var target *MyError。
  • 函数要修改 target 这个变量,必须拿到它的地址:&target。
  • &target 的类型就是 **MyError,也就是“指向一个 *MyError 变量的指针”。

因此,所谓“指针的指针”描述的是传入参数的形状,不是说错误对象必须再套一层指针。errors.As 需要的是一个非空指针,指向它可以写入的目标类型;目标类型刚好是指针时,外层写入指针就会让类型看起来变成二级指针。

这也解释了常见的错误写法:

var target *MyError

// 错误:target 还是 nil,传入的不是 target 变量的地址。
// errors.As(err, target)

// 正确:让 errors.As 能把匹配到的 *MyError 写回 target。
ok := errors.As(err, &target)

直接传 target 时,接口里装的是一个 nil 的 *MyError,它不是可写入的非空目标指针,通常会触发 panic。取地址后,&target 指向一个真实存在的变量,函数才有位置存放匹配结果。

接口目标和普通值目标不都需要“指针的指针”

“errors.As 一定要传二级指针”是不准确的。应该先问:接收结果的变量本身是什么类型?如果接收的是接口变量,它通常只需要传接口变量的地址:

type Temporary interface {
    error
    Temporary() bool
}

var target Temporary
// target 是接口变量,&target 是 *Temporary,不是 **MyError。
if errors.As(err, &target) {
    fmt.Println("临时错误:", target.Error())
}

如果目标类型是一个实现了 error 的值类型,变量也可能是值类型:

type CodeError struct {
    Code int
}

// 值接收者让 CodeError 本身实现 error。
func (e CodeError) Error() string { return fmt.Sprint(e.Code) }

var target CodeError
// 目标变量是 CodeError,因此它的地址是 *CodeError。
if errors.As(err, &target) {
    fmt.Println(target.Code)
}

不过工程代码里更常见的是带字段的指针错误,例如 *os.PathError、*url.Error 或自定义的 *MyError。这类场景下,声明 var target *T,再传 &target,就会自然出现 **T。

panic 和 false 是两类完全不同的问题

排查 errors.As 时,不要把所有失败都归结为“类型没找到”。官方契约把参数形状和匹配结果分开了:

现象通常原因排查方向
panictarget 是 nil、不是指针,或指向的类型不满足要求检查是否传了变量地址,以及目标类型是否实现 error 或是接口
返回 false参数合法,但错误树中没有可赋值给目标类型的错误检查是否使用 %w 包装、目标类型是否写错、错误是否来自另一条分支
返回 true 但字段为空自定义错误的字段本身为空,或自定义 As 方法写入了不同值检查错误构造和自定义匹配逻辑,不要只看布尔值
errors.As 的 target 合法性、错误树遍历、匹配结果和调用方变量的静态边界说明图
图2:errors.As 参数边界说明图,panic 通常来自 target 形状不合法,false 则表示错误树中没有可赋值的匹配值。

例如下面的代码传入了一个错误值,而不是接收结果的地址:

var target *MyError

// 这里把错误值当成 target 传入,目标形状不合法,不能用于写回。
// errors.As(err, *target)

// 这里才是可写的接收位置。
if !errors.As(err, &target) {
    // 参数合法但没有匹配时,选择保留原始错误上下文。
    return fmt.Errorf("读取配置失败,未识别错误类型: %w", err)
}

另外,错误树是否连通取决于包装方式。fmt.Errorf("...: %w", err) 会保留单个被包装错误;errors.Join 会保留多个子错误。若只是把原错误格式化成字符串,匹配类型就丢失了,errors.As 只能对当前错误值和仍然可遍历的包装链工作。

自定义 As 方法仍然遵守“写回位置”

错误类型可以实现 As(any) bool 来定义自定义匹配。这个方法收到的参数仍然是调用方传进来的目标位置,所以实现者要把结果写入它,而不是只返回 true:

type AliasError struct {
    Message string
}

func (e AliasError) Error() string { return e.Message }

func (e AliasError) As(target any) bool {
    // 只有调用方请求 *MyError 时,才负责向 **MyError 写入结果。
    dst, ok := target.(**MyError)
    if !ok {
        return false
    }
    *dst = &MyError{Code: 2001, Msg: e.Message}
    return true
}

这里的 **MyError 就不是偶然出现的:调用方的变量是 *MyError,自定义 As 收到的是这个变量的地址。若方法返回 true 却没有正确写入目标,调用方得到的状态就会不符合预期,因此自定义匹配要和目标类型约定一起维护。

一份可复用的排查清单

  1. 先写出你想读取的错误类型:是 T、*T,还是某个错误接口。
  2. 声明同类型的接收变量:目标是 *T 时写 var target *T。
  3. 把接收变量的地址传入:通常是 errors.As(err, &target)。
  4. 看到 panic,先查 target 是否为非空指针、指向类型是否满足 errors.As 的契约。
  5. 看到 false,再查错误是否通过 %w 或 errors.Join 保留在错误树中。
  6. 匹配成功后再读取字段;不要在布尔结果为 false 时直接解引用可能仍为 nil 的指针。
一句话总结:errors.As 不是要求错误对象必须是“指针的指针”,而是要求传入一个可写的目标位置。目标变量若是 *MyError,取它的地址后自然得到 **MyError;目标若是接口或值类型,指针层级则随接收变量的实际类型变化。

相关问题

errors.As 和类型断言有什么区别?

类型断言只检查当前接口里装的值,errors.As 还会沿 Unwrap 错误树查找,所以适合处理保留上下文的包装错误。

为什么 errors.As 返回 true 但 target 仍然是 nil?

通常应先检查是否实现了自定义 As(any) bool,以及它是否真的向传入的目标位置写入了结果;标准匹配会把可赋值的错误写回 target。

只想判断错误类别时还要用 errors.As 吗?

如果只关心哨兵错误是否存在,优先考虑 errors.Is;只有需要读取结构化字段或调用特定方法时,才用 errors.As 取出对应类型。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go http.Cookie.Valid 为什么会拒绝不合法的 SameSite 值Go http.Cookie.Valid 为什么会拒绝不合法的 SameSite 值
上一篇
Go http.Cookie.Valid 为什么会拒绝不合法的 SameSite 值
Go tls.Conn.HandshakeContext 取消后怎样判断连接是否还能复用
下一篇
Go tls.Conn.HandshakeContext 取消后怎样判断连接是否还能复用
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    838次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    835次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    919次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    894次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    718次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码