记一次有趣的panic
序
风和日丽,是个写bug的好天气
今天在写bug的过程中,碰到了一个有趣的panic
先上个示例代码,这里用到了一个结构体,一个接口
type (
Message interface {
PrintT()
}
MessageImpl struct {
t string
}
)
func (mi *MessageImpl) PrintT() {
fmt.Println(mi.t)
}
然后再加上两个函数
func fmtMsg(m Message) {
if m == nil {
return
}
m.PrintT()
}
func nilMsg() *MessageImpl {
return nil
}
最后加上main函数
func main() {
fmtMsg(new(MessageImpl))
fmtMsg(nilMsg())
}
先来看一下运行结果
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6a45]
goroutine 1 [running]:
main.(*MessageImpl).PrintT(0x0, 0xc00000e018, 0xc00006af48)
main.go:15 +0x5
main.fmtMsg(0x10ec440, 0x0)
main.go:23 +0xa4
main.main()
main.go:32 +0x56
很明显,这行代码识别出现了问题
if m == nil {
return
}
那么问题来了 ,为什么我明明返回的nil但是却不能识别呢?
解决方案
先来说说解决方案吧,想要解决这个无法识别nil的情况,有两种方式
-
使用反射识别,如:
func fmtMsg(m Message) { if reflect.ValueOf(m).IsNil() { return } m.PrintT() }
-
修改返回值,如:
func nilMsg() Message { return nil }
使用这两种方式都可以有效的识别空值,并正常的return或者调用PrintT函数
原因
再来说说为什么可以用这两种方式解决
-
使用reflect来进行空识别的原因
很简单,就是因为 interface{} 在golang底层的识别有异于常规的结构体或指针,他本身包含 类型,值,当赋值为空时,他的类型是有的,但是值为nil,所以单纯的==nil比较是不起作用的
type interface{} 这个类型 与直接使用interface{} 在结构上有些区别,但是特性基本一样
使用reflect.IsNil其实就是跳过类型直接看值,就可以解决这个问题。
-
修改返回值为指针的原因
这个就要分析我们明明没有使用interface{}但是却出现了interface{}的特性是为什么了
首先,我们nilMsg的第一版本返回的类型是 *MessageImpl,这时,这个变量的类型就已经确定了。
其次,在我们调用fmtMsg(m Message)这个函数的时候,可以看到他的类型为 Message接口。
这时 由于golang的特性是复制传参,那么实际上这里做了两部操作,1.转换我们当前的变量成为Message接口,2.复制给参数使用
再结合我们说的interface{}的特性,是不是就和带类型的nil值赋值给interface{}的情况一样了?
所以我们在nilMsg函数return的类型直接指定为Message的时候,有个好处,再传参给fmtMsg的时候,不存在转换复制,是单纯的复制,所以nil就可以识别出来了。
后记
本期文字较多,表情包比较少,大家见谅。
由于大部分是语言特性的问题,至于正确性嘛。。。。
我觉得他是对的,也欢迎不同意见的小伙伴来喷我。