问题

小提示, 若想直接查看原理, 可从接口原理开始查看.

GO
func main() {
	var obj interface{}
	fmt.Printf("obj == nil. %b\n", obj == nil)
	type st struct{}
	var s *st
	obj = s
	fmt.Printf("s == nil. %b\n", s == nil)
	fmt.Printf("obj == nil. %b\n", obj == nil)
}

先盲猜一下结果.

niltruestrueobjstrue

如果你也是这么认为, 那么结果会令你像我一样十分惊讶:

image-20220802203427661

objnil
搭建 gdb 调试环境
gdb
gdb
dockergolang:1.18
# 安装 gdb 工具
apt update && apt install -y gdb
echo 'add-auto-load-safe-path /usr/local/go/src/runtime/runtime-gdb.py' > /root/.gdbinit
# 编译 go 文件. 关闭所有的优化, 防止调试时与编写的内容不一致
go build -gcflags "all=-N -l" main.go
# 进行调试
gdb ./main

是不是很简单呀.

调试与揭秘

为了方便调试, 我将无关内容去掉, 调试使用的程序如下:

package main

func main() {
	var obj interface{}
	type st struct{}
	var s *st
	println(obj == nil)
	obj = s
	println(obj == nil)
}
obj

image-20220802210607987

image-20220802210623591

objobj == nilTRUEobj
objobj == nilFALSE_type

在此处, 我有理由得出这样的结论:

golanginterface_type/datainterfacenilnilinterfacenilnil
obj

image-20220802210829177

interfaceruntime.eface
internfaceinterface_type_typenilinterfacenil

以上, 是我本次问题查找的原因及初步查找的过程. 我基于此对接口的实现原理进行了查阅. 后面就直接进行原理介绍, 不再穿插查找过程了, 否则着实影响观看体验.

接口原理
GO
runtime.eface
type TestInter interface {
}
var obj interface{}
var obj2 TestInter
runtime.iface
type TestInter interface {
  testFunc()
}
var obj2 TestInter

eface

efaceruntime2.go
type eface struct {
	_type *_type // 保存类型信息
	data  unsafe.Pointer // 保存内容
}

type _type struct {
	size       uintptr // 类型大小
	ptrdata    uintptr // 没整明白是干什么用的...
	hash       uint32 // 类型的哈希值. 可用于快速判断类型是否相等
	tflag      tflag // 类型的额外信息
	align      uint8 // 变量的内存对齐大小
	fieldAlign uint8 
	kind       uint8 // 类型
	equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较此类型对象是否相等
	gcdata    *byte // 垃圾收集的 GC 数据
	str       nameOff
	ptrToThis typeOff
}

// Pointer 就是一个指针
type Pointer *ArbitraryType
type ArbitraryType int
_type
_type

iface

ifaceefaceiface
type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter *interfacetype // 保存接口的信息. 用于确定变量的接口类型
	_type *_type // data 指向值得类型信息, 上面已经出现过了
	hash  uint32 // 从 _type.hash 拿过来的. 当将 interface 类型变量向下转型时, 用于快速判断. 
	_     [4]byte
	// 记录接口实现的所有方法. 
	// 若 fun[0]==0, 说明 _type 没有实现此接口. 
	//		(没错, 是有可能没实现的. 比如转型失败)
	// 否则, 说明实现了此接口. 所有方法的函数指针在内存中顺序存放. 
	// fun[0] 记录的是第一个方法的地址
	// 顺便提一句, 函数按照名称的字段序在内存中存放
	fun   [1]uintptr 
}

type interfacetype struct {
	typ     _type // 接口类型
	pkgpath name // 包名
	mhdr    []imethod // 接口定义的方法集
}
interfaceGO
interfaceGOruntime.iface.goconvunsafe.Pointer
总结
GOinterfacenilnil
GO1.18interface