Go 杂谈——interface 与 nil 的细节让我出了线上 BUG
首发:https://mp.weixin.qq.com/s/mA9C_3egfxZtf8MN1Mg_2A
前段时间上了一次线,一开始运行的好好的,但是突然有一天,出现了一个 panic 错误。这可给我吓得不轻,要知道线上的 go 程序 panic 可是很要命的。
但是追查下来,让我百思不得姐。下面我来把现场用一个 demo 复述一下。
上面的代码没有逻辑,仅仅是复现一下当时的情景。上面这段代码,最终在 18 行 panic 了。
是不是非常奇怪,明明 b 已经是 nil 了,为什么还会进入条件判断,莫非 a 不是 nil?带着这个疑问,我翻阅了一下源码,发现了这两个结构体。
这两个东西就厉害了,它们正是 interface 的原形。iface 定义了有方法的 interface,eface 定义了无方法的 interface,也就是“empty interface”。当 var a A 这样定义时,实际上 a 是被定义成了 eface 这个结构体。所以,实际上 a 并不是 nil,它只是 eface.data 是 nil。
当然,从汇编中也能找到端倪。
可以看到 b 存储的是 0,而 a 中存储了结构体内的两个指针。
另一个栗子
那么问题来了,在 go 中,我们会经常判断 if err != nil,这个会不会也有问题呢?再一次来个 demo。
这个 demo 将 err 直接赋值为 nil。我们来猜猜下面会输出什么呢。
答案就是 true。果然,我们判断 if err != nil 的时候,在这种情况下,确实没有问题。(当然,大部分 err 的返回也是这样返回的。)
同样的,我们再次来看看这段代码编译后的结果。
这样,可以清楚的看到,err 直接被赋值成 0(也就是 nil 了)。所以,上面在比较的时候,err 确实是 0 (nil) 了。
解决方案
在目前的 Go 版本中,我并没有找到优雅的解决方案。只能给出三个这种的方案。
第一种,直接用反射来判断。
另一种,便是在定义接口的时候就定义上 IsNil 这个方法。
这样,就能使用 a.IsNil() 来判断了。
第三种,在每个指针接收者方法中判断接收者是不是 nil。
虽然这三个办法都能解决 nil 的问题,但是,对于鸭子模型的 Go 来说,后两者并不友好,毕竟需要侵入实现的函数。而鸭子模型最大的魅力在于,我们可以不用关心实现,只需要定义接口。
版权声明: 本文为 InfoQ 作者【HZFEStudio】的原创文章。
原文链接:【http://xie.infoq.cn/article/68bfe343c4ba8b36b07d5d2d5】。文章转载请联系作者。
评论