问题描述
昨天开发的一段代码在运行时遇到了奇怪的 panic 问题,报错:
runtime error: invalid memory address or nil pointer dereference
但是奇怪的是,代码中 panic 出处,我是有判断 nil 的:
// 这是相关的结构体定义
type Options struct {
sourceFrame fmt.Stringer // 错误根源栈页
}
// 省略一堆无关代码
if options.sourceFrame != nil { // 这里判断了 options.sourceFrame 是否为 nil
metaData[metaKeySourceFrame] = options.sourceFrame.String() // 这一行代码触发 panic
}
好奇怪,我不是都判断了是否为 nil 了吗?为什么还会报错呢?
问题分析
经过一番搜索和学习,最终发现 golang 的一个坑:interface 类型的 nil 不等于字面量 nil。
Under the hood, an interface in Golang consists of two elements: type and value.
在我的实际代码中,sourceFrame
变量的值是 {fmt.Stringer | *frame.Frame} *frame.Frame(nil)
,也就是说,对于 golang 运行时,我的 sourceFrame
是“类型为 frame.Frame 的指针,且值为空”,当用它去和 nil
对比的时候,它们虽然值是一样的,但是类型不一样,字面量 nil
的类型为 nil
。
示例程序
程序1:有类型的 nil
package main
import "fmt"
func main() {
var a interface{}
fmt.Printf("%T\n", nil)
fmt.Printf("%T\n", a)
a = (*string)(nil)
fmt.Printf("%T\n", (*string)(nil))
fmt.Printf("%T\n", a)
}
这个代码测试不同类型的 nil,输出结果为:
<nil>
<nil>
*string
*string
程序2:测试 interface 类型的 nil 和字面量 nil 是否相等
package main
import (
"fmt"
)
func main() {
emptyStringPtr := (*string)(nil)
fmt.Println(emptyStringPtr == nil) // true
var a interface{}
fmt.Println(a == nil) // true
a = emptyStringPtr
fmt.Println(a == nil) // false
fmt.Println(a == (*string)(nil)) // true
fmt.Println(a == emptyStringPtr) // true
fmt.Println(a.(*string) == nil) // true
}
这个代码分别测试了具体类型的空指针和类型为空接口的空指针和字面量 nil 的比较,输出结果已经对应注释在对应代码末尾。
所以通过这个测试可以确认:
- 具体类型的空指针和字面量 nil 是相等的;
- 类型为接口的空指针是否等于字面量 nil,取决于它的值是否为 nil,并且它的类型也为 nil;
- 如果想要将类型为接口的变量和字面量 nil 进行比较,需要将它们转换为具体类型的指针,然后再进行比较。
但是这里也有一个好玩的事情,就是按照这个规律,三个变量之间的相等性,是没有传递性的,因为 a == emptyStringPtr
,且 emptyStringPtr == nil
,但是 a != nil
。😳
猜测这是因为 golang 在中间做了隐式类型转换,因为前两种等式可以推导具体类型,而最后一种等式无法推导具体类型,所以无法进行隐式类型转换。
如何优雅地判断 interface 类型的 nil?
除了前面说的转换为具体类型再比较,但是通常来说运行时具体类型不可预知。另一种可行的方法是通过反射来实现:
package main
import (
"fmt"
"reflect"
)
func main() {
var a interface{} = (*string)(nil)
fmt.Println(a == nil) // false
fmt.Println(reflect.ValueOf(a).Kind() == reflect.Ptr && reflect.ValueOf(a).IsNil()) // true
}
参考资料
版权声明:本文为原创文章,转载请注明来源:《Golang interface 类型的 nil 居然不等于字面量 nil? - Hackerpie》,谢绝未经允许的转载。