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

从运行结果来看,mykk都是空值,同事的疑惑是:明明kk的原型KKHappyBirthday的方法声明,直觉上不应该是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的本质。

另外,某小仙女今天生日,在远方祝她生日快乐🎂。