关于interface和nil的那个坑

68 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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	// 数据指针
}

image.png
根据空接口的定义,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…