go中error()必须用指针接收者:避免大字段拷贝、确保errors.as()成功;错误链断裂主因是%v替代%w、未实现unwrap()、跨包同名类型不兼容;temporary()等仅在调用方明确检查时才实现。

直接用结构体 + 指针接收者实现 Error() 就够了,不需要继承、嵌入或额外接口——Go 的 error 是隐式满足的接口,写法越简单越稳定。
为什么 Error() 必须用指针接收者
值接收者会拷贝整个结构体;一旦字段里有 []byte、map 或后续加了大字段,性能就不可控。更隐蔽的问题是:如果用值接收者,errors.As(err, &e) 会失败——因为 &e 是取地址,而值接收者返回的是副本,类型不匹配。
- 始终用
func (e *MyError) Error() string - 哪怕结构体只有两个
int字段,也统一用指针接收者,避免未来扩容时踩坑 - 别为了“看起来轻量”用值接收者,Go 编译器不会帮你优化掉这个拷贝
errors.As() 总是返回 false 的三个常见原因
不是代码写错了,而是错误链断了或类型不一致。最常发生在包装和判断环节。
- 中间层用了
fmt.Errorf("xxx: %v", err)而不是%w——%v把原始 error 转成字符串,Unwrap()返回nil,链就断了 - 自定义错误没实现
Unwrap() error方法(哪怕只是return e.Cause),errors.As()就无法向下穿透 - 不同包里定义了同名结构体
*UserNotFound,哪怕字段完全一样,Go 也认为是不同类型,errors.As()不会跨包识别
要不要实现 Temporary() 和 Timeout()
只在你明确知道调用方会检查它们时才加。比如用 net/http.Transport 或 backoff.Retry 时,这些方法会被自动调用;否则就是冗余代码,还可能误导维护者以为这个错误支持重试。
-
Temporary()推荐逻辑:return e.Code == 408 || (e.Code >= 500 && e.Code -
Timeout()更严格:return e.Code == 408 || e.Code == 429 - 两个方法都用指针接收者,和
Error()保持一致 - 别给所有错误都加,尤其业务逻辑错误(如
InvalidEmail)加了反而让重试逻辑误判
真正难的不是写对 Error(),而是控制错误是否被包装、在哪一层包装、是否暴露 Code 给 HTTP 层——这些决策一旦分散在各处,errors.As() 就会变成猜谜游戏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











