应使用 errors.As(err, &e) 安全提取自定义 error 字段,它递归调用 Unwrap() 并自动处理包装错误;手动类型断言易失败,且需注意指针接收者与字段导出设计。

怎么用 type assertion 拿到自定义 error 里的字段
Go 的 error 是接口,类型断言是唯一能安全取出底层结构体字段的方式。直接类型断言失败会 panic,必须用「带 ok 的双值形式」。
- 错误写法:
err.(*MyError).Code—— 如果err不是*MyError类型,直接 panic - 正确写法:
if e, ok := err.(*MyError); ok { return e.Code } - 如果自定义 error 实现了
Unwrap()(比如嵌套了其他 error),要先errors.Unwrap()再断言,否则可能断言不到最内层 - 注意指针接收者问题:如果你的 error 是值类型(
MyError)但实现方法用的是指针接收者(func (e *MyError) Error() string),那只有*MyError能满足error接口,MyError值本身不能直接断言为*MyError
为什么有时候 type assertion 总是 false
不是代码写错了,大概率是 error 的实际类型和你断言的不一致 —— 尤其在用 fmt.Errorf、errors.Wrap 或 errors.Join 包装后。
-
fmt.Errorf("wrap: %w", err)返回的是*fmt.wrapError,不是你的*MyError -
github.com/pkg/errors.Wrap(err, "msg")返回的是*errors.withStack,同样无法直接断言 - 解决办法:用
errors.As(err, &target)替代手动断言,它会递归调用Unwrap()直到找到匹配类型 - 示例:
var e *MyError; if errors.As(err, &e) { return e.Code }—— 这比手写循环Unwrap更可靠
自定义 error 结构体该导出哪些字段
字段是否导出,直接决定外部包能不能通过类型断言访问。别默认全导出,也别全不导出。
- 必须导出的字段:你需要被其他包读取的,比如
Code、TraceID、Retryable - 建议不导出的字段:仅用于内部格式化输出的,比如
message(应通过Error()方法暴露)、stack(应封装在StackTrace()方法里) - 如果字段只是辅助调试(如
timestamp),又不想开放修改,可以导出但只提供 getter 方法,避免外部直接赋值破坏一致性 - 注意:导出字段 + 指针接收者方法 ≠ 安全;只要字段导出,别人就能
e.Code = 999,所以敏感字段尽量不导出,靠方法控制
errors.As 和 errors.Is 在自定义 error 中怎么配合用
errors.As 用来提取字段,errors.Is 用来判断错误种类。两者定位不同,但常一起出现。
立即学习“go语言免费学习笔记(深入)”;
-
errors.Is(err, ErrNotFound)判断是不是这个预定义变量(适合哨兵错误) -
errors.As(err, &e)提取结构体字段(适合带上下文的错误) - 如果既想分类又想取字段,先
Is再As:比如先确认是业务错误,再断言取Code - 注意:
errors.Is依赖Is()方法或错误链中某个节点等于目标值;自定义 error 若想支持Is,最好也实现Is(target error) bool,尤其当它包装了其他 error 时
真正麻烦的不是断言语法,而是 error 被层层包装后类型信息丢失。别迷信一次断言能搞定,errors.As 是标准库给的退路,用就对了。另外,字段导出与否不是二选一,而是按访问意图设计 —— 外部需要读?导出;需要改?不导出加方法。










