go自定义错误类型必须用指针接收者、字段导出且不含不可比较类型,实现unwrap()以保链式穿透,必要时加is()方法支持语义匹配,否则errors.as/is会静默失效。

Go 里自定义错误类型,不是为了“看起来高级”,而是解决三个实际问题:调用方能 errors.As 提取出具体错误结构、能用 errors.Is 做语义判断、包装后(%w)还能穿透查原始类型。只实现 Error() 方法远远不够。
为什么 errors.As 总是失败
最常见原因是用了值类型接收者或字段不可比较。比如写 func (e MyError) Error() string,那 errors.As(err, &target) 永远不成立——接口里存的是值拷贝,而 &target 是指针,类型不匹配。
- 必须用指针接收者:
func (e *MyError) Error() string - 结构体字段不能含
map、slice、func,否则errors.As会 panic - 所有字段建议导出(首字母大写),否则反射无法读取,
As失效 - 别在函数里返回
MyError{...},要返回&MyError{...}
Unwrap() 不写就断链
只要用了 fmt.Errorf("wrap: %w", err),且 err 是你自定义的类型,就必须实现 Unwrap() 方法。否则 errors.Is(wrappedErr, myErr) 返回 false,错误链在第一层就断了。
- 典型写法:
func (e *MyError) Unwrap() error { return e.err },其中e.err是你结构体里存的底层 error 字段 - 如果没包装其他 error,
Unwrap()应该返回nil,不能漏掉这个方法 - 返回非
nil的 error 时,确保它本身也支持Unwrap(),否则链式穿透到一半就停
要不要加 Is() 方法
标准库的 errors.Is 默认只比对同一实例或相等值,但业务中常需“同类型+同 Code 就算匹配”。这时加一个 Is() 方法更可靠。
- 比如两个
*BizError实例,Code 都是 404,但内存地址不同,errors.Is返回false;而你自己的Is()可以按 Code 判断 - 签名必须是
func (e *MyError) Is(target error) bool,否则errors.Is不会调用它 - 内部先做类型断言:
t, ok := target.(*MyError),再比关键字段 - 注意:若目标
target是哨兵错误(如var ErrNotFound = errors.New("not found")),你的Is()不会被触发——那是errors.Is自己的逻辑
真正容易被忽略的点是:错误类型一旦导出,它的字段和方法签名就构成 API 合约。改一个字段名、把指针接收者换成值接收者,下游 errors.As 就静默失效——没有编译错误,只有运行时行为突变。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











