interface的零值及其在什么时候不再是零值
背景
前几天同事突然抛过来一张截图,很惊诧地说,Go的Interface设计很怪。我一看,原来是一个示例程序,大体的内容如下:
示例代码
package main
import (
"fmt"
)
type KK interface {
HappyBirthday()
}
func main() {
var my interface{}
if my == nil {
fmt.Println("mikk")
} else {
fmt.Println("not nil")
}
var kk KK
if kk == nil {
fmt.Println("mikk")
} else {
fmt.Println("not nil")
}
}
示例代码运行结果
mikk
mikk
从运行结果来看,my
和kk
都是空值,同事的疑惑是:明明kk
的原型KK
有HappyBirthday
的方法声明,直觉上不应该是nil
啊。
Go源码相关内容
看到同事的疑惑以后,我的第一反应是想到自己翻看过的Go源码。
源码中的nil
在go/src/builtin/builtin.go
的源码中,对nil的定义如下:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
// 零值,特制 指针、信道、函数、接口、map、slice
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
大体意思是:nil
代表零值;特别地,代表指针、信道、函数、接口(Interface)、map、slice的零值。也就是说,Interface的零值本就是nil
。
而根据Go的特点,var name Type
的声明方式中name
的默认值本就是零值,因此无论是my
还是kk
,都等于nil
也便可以理解了。
源码中的Interface
Interface主要其作用的地方是在运行时,因此这里主要介绍go/src/runtime/runtime2.go
中的两个片段。
// 带有方法的接口
type iface struct {
// 存储_type信息还有结构实现方法的集合
tab *itab
//指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
data unsafe.Pointer
}
// 空接口
type eface struct {
//类型信息
_type *_type
// 指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
data unsafe.Pointer
}
从源码可以知道,无论是iface
还是eface
,其底层均存在一个data
字段,也就是说,无论是带方法的接口还是空接口,其的存在必然要依附有意义的数值(零值也是有意义的)才可以。可以简单认为一块内存,里面存放着变量,哪怕这个变量存放的全是零值也无所谓,但是必须要有这么一个内存变量,也就是说,只有这块内存存在了,Interface的存在才有意义。
再看一个例子
package main
import (
"fmt"
)
type Angel interface {
Show()
}
type Girl struct{}
func (girl *Girl) Show() {
}
func miss() Angel {
var g *Girl
return g
}
func main() {
angel := miss()
if angel == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
}
上面的代码输出 not nil
。
具体的,虽然miss()
返回了零值的g
,但是data
这时候已经有值了(虽然在这里还是个零值)。这种情况下,angel
就不是nil
了,因为它已经有了data
。
小结
本文主要对比了Go的Interface的两个例子,并从源码角度简单剖析了Interface的本质。