Golang 接口(interface)源码分析
注意当前go版本代码为1.23
前言
接口是高级语言中的一个规约,是一组方法签名的集合。在Golang中, 接口是非侵入式的,具体类型实现 interface 不需要在语法上显式的声明,只需要具体类型的方法集合是 interface 方法集合的超集,就表示该类实现了这一 interface。编译器在编译时会进行 interface 校验。interface 和具体类型不同,它不能实现具体逻辑,也不能定义字段。
底层数据结构
Golang中接口的底层结构体有两种,分别是iface 和 eface,其中 runtime.iface 描述的接口包含方法,而 runtime.eface 则是不包含任何方法的空接口:interface{}。
iface源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| type iface struct { tab *abi.ITab data unsafe.Pointer }
type ITab struct { Inter *InterfaceType Type *Type Hash uint32 Fun [1]uintptr }
type InterfaceType struct { Type Type PkgPath Name Methods []Imethod }
type Type struct { Size_ uintptr PtrBytes uintptr Hash uint32 TFlag TFlag Align_ uint8 FieldAlign_ uint8 Kind_ Kind Equal func(unsafe.Pointer, unsafe.Pointer) bool GCData *byte Str NameOff PtrToThis TypeOff }
|
iface 内部维护两个指针,tab 中存放的是类型、方法等信息。data 则指向接口具体的值,一般而言是一个指向堆内存的指针。
由于 Go 语言是强类型语言,编译时对每个变量的类型信息做强校验,所以每个类型的元信息要用一个结构体描述。再者 Go 的反射也是基于类型的元信息实现的。Type 就是所有类型最原始的元信息。
整体结构如下:

eface源码
源码路径: runtime.eface
1 2 3 4 5 6
| type eface struct { _type *_type data unsafe.Pointer }
type _type = abi.Type
|
eface作为空的 inferface{} 是没有方法集的接口。所以不需要 itab 数据结构。它只需要存类型和类型对应的值即可。对应的数据结构如下:
从这个数据结构可以看出,相比 iface,eface 只需要维护 abi.Type ,表示空接口所承载的具体的实体类型和data 描述了具体的值。它们分别被称为动态类型和动态值。而接口值包括动态类型和动态值,只有当 2 个字段都为 nil,空接口才为 nil。空接口的主要目的有 2 个,一是实现“泛型”,二是使用反射。所以空接口并不一定等于nil,这是常见的犯错点。
关于type的结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type Type struct { Size_ uintptr PtrBytes uintptr Hash uint32 TFlag TFlag Align_ uint8 FieldAlign_ uint8 Kind_ Kind Equal func(unsafe.Pointer, unsafe.Pointer) bool GCData *byte Str NameOff PtrToThis TypeOff }
|
Go 语言各种数据类型都是在 _type 字段的基础上,增加一些额外的字段来进行管理的,这些数据类型的结构体定义,也是反射实现的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| type ArrayType struct { Type Elem *Type Slice *Type Len uintptr }
type ChanType struct { Type Elem *Type Dir ChanDir }
type SliceType struct { Type Elem *Type }
type StructType struct { Type PkgPath Name Fields []StructField }
|
问题
1.空接口一定等于 nil 吗?
接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| type Coder interface { }
type Gopher struct { name string }
func main() { var c Coder fmt.Println(c == nil) fmt.Printf("c: %T, %v\n", c, c)
var g *Gopher fmt.Println(g == nil)
c = g fmt.Println(c == nil) fmt.Printf("c: %T, %v\n", c, c) }
|
2.【引申】 fmt.Println 函数的参数是 interface。对于内置类型,函数内部会用穷举法,得出它的真实类型,然后转换为字符串打印。而对于自定义类型,首先确定该类型是否实现了 String() 方法,如果实现了,则直接打印输出 String() 方法的结果;否则,会通过反射来遍历对象的成员进行打印。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package main
import "fmt"
type Student struct { Name string Age int }
func main() { var s = Student{ Name: "test", Age: 18, } fmt.Println(s) }
|
因为 Student 结构体没有实现 String() 方法,所以 fmt.Println 会利用反射挨个打印成员变量:{qcrao 18}
这是添加string()的实现:
1 2 3
| func (s Student) String() string { return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age) }
|
打印结果:
但是如果是*Student为接受者类型呢?
1 2 3
| func (s *Student) String() string { return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age) }
|
打印结果:
原因:类型 T 只有接受者是 T 的方法;而类型 *T 拥有接受者是 T 和 *T 的方法。语法上 T 能直接调 *T 的方法仅仅是 Go 的语法糖。
所以Student 结构体定义了接受者类型是值类型的 String() 方法时,通过**fmt.Println(s)或者fmt.Println(&s)**均可以按照自定义的格式来打印。
如果 Student 结构体定义了接受者类型是指针类型的 String() 方法时,只有通过**fmt.Println(&s)**才能按照自定义的格式打印。
参考链接
1.iface与eface的区别是什么
2.深入研究 Go interface 底层实现