开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
运行代码,发现问题
试着运行接下来一段代码
func TestNil(t *testing.T) {
test := func(v interface{}) {
fmt.Println(v == nil)
}
var a *string = nil
fmt.Println(a == nil)
test(a)
test(nil)
}
运行上述代码后,可能你和我一样,觉得运行结果是这样。
true
true
true
但其实,运行结果是
true
false
true
相信第一个和第三个运行结果我们都能理解,但是为什么a经过test方法后,经过空接口一转换,反而不等于nil了呢?
查看源码,分析问题
对于interface来说,可以分为空接口和带方法的接口。
var v interface{} //空接口
var v interface{ // 带方法的接口
GetXX()
}
Go 语言使用runtime.iface表示带方法的接口,使用runtime.eface表示不带任何方法的空接口interface{}。在这里暂时只分析空接口。
// runtime/runtime2.go
type eface struct {
_type *_type // 指向对象的类型信息
data unsafe.Pointer // 数据指针
}
根据空接口的定义,go interface包含了类型和值两部分,只有类型和值都为空时,interface才等于nil
回到最初的问题,我们打印下传入函数中的空接口变量值,来看看它两个指针值的情况。
// InterfaceStruct 定义了一个 interface{} 的内部结构
type InterfaceStruct struct {
pt uintptr // 到值类型的指针
pv uintptr // 到值内容的指针
}
// ToInterfaceStruct 将一个 interface{} 转换为 InterfaceStruct
func ToInterfaceStruct(i interface{}) InterfaceStruct {
// 将接口i转换为InterfaceStruct类型
return *(*InterfaceStruct)(unsafe.Pointer(&i))
}
func IsNil(i interface{}) {
fmt.Printf("value is %+v\n", ToInterfaceStruct(i))
}
func TestIsNil(t *testing.T) {
var a *string = nil
IsNil(a)
fmt.Println(a == nil)
IsNil(nil)
}
运行输出
value is {pt:4331312320 pv:0}
true
value is {pt:0 pv:0}
通过输出我们可以看到,a的指针类型不为0,所以可以解释最初的运行中,a传入test函数后,却返回false.
不过大家也发现了,那为什么在这里,a==nil的时候输出了true?这不是出现问题了吗?
针对具体类型的变量,判断是否是 nil 要根据其值是否为零值。
因为 a是 一个*string类型,需要探寻一下,当a转换为*string类型,是否为nil。
type pointer struct {
a *string
}
func TestStrIsNil(t *testing.T) {
var a *string = nil
fmt.Printf("value is %+v\n", *(*pointer)(unsafe.Pointer(&a))) // value is {a:<nil>}
fmt.Println(a == nil) // true
}
不出所料,果然是零值。至此解释了开篇出乎意料的比较结果背后的原因:
a为 nil因为其值为零值,类型为 *string 的a传给空接口后,因为空接口的值并不是零值,所以接口变量不是 nil。
如果我们在test方法中加入
fmt.Println("TestNil", reflect.TypeOf(v)) // *string
fmt.Println("TestNil", reflect.ValueOf(v)) // <nil>
而对于interface v来说,type=*string,因此不为nil。这种方式也可以帮助我们理解。
总结思考
go interface包含了类型和值两部分,只有类型和值都为空时,interface才等于nil。
参考
cloud.tencent.com/developer/a…
www.cnblogs.com/jiujuan/p/1…
go.dev/doc/faq#nil…