Go自定义结构体错误中使用errors.Is与errors.As结果不符的问题求助
errors.Is/errors.As的匹配问题 咱们来逐个拆解你遇到的问题,一步步梳理清楚原因并给出修复方案:
1. isMyError返回false的核心原因
你的isMyError函数里,每次调用new("")都会生成一个全新的myError实例,而errors.Is默认是做实例相等性匹配(除非你自定义的Is方法有特殊逻辑)。你当前的Is方法把判断逻辑直接委托给了内部的e.err,但这个新创建的myError的内部err,和实际错误链里的err完全不是同一个实例,自然匹配不上。
修复思路
如果你的需求是判断错误链中是否存在myError类型的错误,errors.Is其实不是最合适的工具——它更适合匹配预定义的全局错误实例。判断类型应该用errors.As,调整后的isMyError可以改成这样:
func isMyError(err error) bool { var target myError return errors.As(err, &target) }
如果你非要用errors.Is实现类型判断,可以修改myError的Is方法逻辑:
func (e myError) Is(target error) bool { // 先判断目标是不是myError类型,是就直接返回true;否则再匹配内部err _, ok := target.(myError) if ok { return true } return errors.Is(e.err, target) }
同时isMyError里用空的myError作为匹配目标即可:
func isMyError(err error) bool { return errors.Is(err, myError{}) }
2. 最后一行输出panic的原因
当传入的错误不是myError时,errors.As(err, &target)会返回false,此时target还是零值的myError,它的err字段是nil。打印target时会调用它的Error()方法,而e.err.Error()会触发nil指针解引用panic。
修复方案
有两种方式可以避免这个问题:
- 方式一:在
asMyError里判断ok,如果为false就返回nil而非零值myError:
func asMyError(err error) (error, bool) { var target myError ok := errors.As(err, &target) if !ok { return nil, ok } return target, ok }
- 方式二:给
myError的Error()方法加nil判断,兜底空错误的情况:
func (e myError) Error() string { if e.err == nil { return "empty myError" } return e.err.Error() }
3. 额外优化建议
其实你不需要自己实现As和Is方法——只要实现了Unwrap()方法,标准库的errors.As和errors.Is会自动沿着unwrap链去查找匹配。你自定义的As和Is反而可能干扰默认逻辑,比如你的As方法直接委托给内部err,会让errors.As跳过对myError本身的判断(不过你当前代码里asMyError能匹配到,是因为errors.As会先尝试直接匹配目标类型)。
修复后的完整代码
package main import ( "errors" "fmt" ) type myError struct{ err error } func (e myError) Error() string { if e.err == nil { return "empty myError" } return e.err.Error() } func new(msg string, args ...any) error { return myError{fmt.Errorf(msg, args...)} } func (e myError) Unwrap() error { return e.err } // 移除自定义的As和Is方法,交给标准库处理unwrap链 func isMyError(err error) bool { var target myError return errors.As(err, &target) } func asMyError(err error) (error, bool) { var target myError ok := errors.As(err, &target) if !ok { return nil, ok } return target, ok } func main() { err := fmt.Errorf("wrap: %w", new("I am a myError")) fmt.Println(isMyError(err)) res, ok := asMyError(err) fmt.Println(res, ok) err = fmt.Errorf("wrap: %w", errors.New("I am not a myError")) fmt.Println(isMyError(err)) res, ok = asMyError(err) fmt.Println(res, ok) }
修复后的输出
true I am a myError true false <nil> false
内容的提问来源于stack exchange,提问作者Remi.b

