Go语言自定义错误的Is、As方法未按预期工作问题排查
问题根因
你对标准库errors包的Is、As方法的设计用途存在理解偏差,同时变量定义方式有误,才会出现不符合预期的结果。两个方法的核心定位完全不同:
errors.Is:用于 值相等判断,默认逻辑会沿着错误包装链逐层比对,判断链上是否存在错误与传入的目标错误是同一个值(典型使用场景是判断预定义的哨兵错误,比如io.EOF),默认不会做类型匹配。errors.As:用于 类型提取判断,只要错误链上存在某层错误符合目标类型,就会把该错误值赋值给传入的目标指针,返回true,这才是用来判断自定义错误类型的标准方法。
你的代码存在两个核心问题:
- 全局变量
FatalError是值为nil的*fatalError类型指针,本身不是一个有实际内容的有效错误值。调用errors.Is(err, FatalError)时,实际是拿&fatalError{msg:"test"}和nil指针比值,二者内存地址完全不同,也没有自定义Is方法重写判断逻辑,自然返回false。 - 调用
errors.As(err, &FatalError)时虽然返回了true,但属于歪打正着:你传入的&FatalError是**fatalError类型,正好符合As方法对目标参数的类型要求,匹配到*fatalError类型的err后,会直接把err的指针赋值给全局变量FatalError。这种写法存在严重隐患:全局变量会被意外修改,并发场景下会触发数据竞争,后续判断逻辑会完全混乱。
正确写法
根据你的使用场景(判断错误是否为自定义的fatalError类型),直接用errors.As做类型判断即可,不需要提前定义全局的nil类型变量:
package main import ( "errors" "fmt" ) type fatalError struct { msg string } func (e *fatalError) Error() string { return e.msg } func NewFatalError(msg string, vars ...interface{}) error { return &fatalError{ msg: fmt.Sprintf(msg, vars...), } } func main() { err := NewFatalError("test") // 在判断作用域内声明目标类型变量,取地址传入As即可 var fe *fatalError if errors.As(err, &fe) { fmt.Println("错误属于fatalError类型,信息为:", fe.Error()) } }
如果你确实需要用errors.Is做类型级别的判断(不推荐,违背方法设计初衷),可以给自定义错误实现Is方法,自定义判断逻辑:
// 给fatalError加Is方法,只要目标是*fatalError类型就返回true func (e *fatalError) Is(target error) bool { _, ok := target.(*fatalError) return ok }
加完这个方法后,你原来的errors.Is(err, FatalError)调用会返回true,但常规类型判断场景直接用As更稳妥。
注意事项
- 不要用
errors.Is做类型判断,它的默认逻辑只做值相等比对。 - 哨兵错误(供Is判断的固定错误值)需要定义成有实际值的错误实例,比如
var ErrFatal = errors.New("fatal"),不要定义成对应类型的nil指针。 - 调用
errors.As时,目标变量尽量在当前判断作用域内声明,不要使用全局变量,避免变量被意外修改。
内容的提问来源于stack exchange,提问作者hookenz
相关产品推荐
相关产品推荐

