【Golang】Interface 底层实现(未完)
接口是高级语言中的一个规约,是一组方法签名的集合。Go 的 Interface 是非侵入式的,具体类型实现 Interface 不需要在语法上显式的声明,只需要具体类型的方法集合是 Interface 方法集合的超集,就表示该类实现了这一 Interface。编译器在编译时会进行 Interface 校验,Interface 和具体类型不同,它不能实现具体逻辑,也不能定义字段。
在 Go 语言中,Interface 和函数一样,都是第一公民,Interface 可以用在任何使用变量的地方。可以作为结构体内的字段,可以作为函数的形参和返回值,可以作为其他 Interface 定义的内嵌字段。Interface 在大型项目中常常用来解耦,在层与层之间用 Interface 进行抽象和解耦,使得抽象出来的代码特别简洁,这也符合 Go 语言设计之初的哲学。
先看一个易错的例子:
1 | package main |
这将输出:
true
false
Interface 实际上包含两部分,类型和值。对于 x 而言,它的类型和值都是 nil,所以 x == nil 是 true;对于 y,它的类型是 *int,值是 nil,所以 y == nil 是 false。因此,我们在看 Interface 的时候,需要关注类型和值两部分。
底层实现
Go 语言中描述接口底层结构体的是 iface 和 eface 这两个结构体,其中 iface 表示非空结构体,eface 表示空结构体:
1 | type iface struct { |
代表 tab 字段的 *itab 代表接口的类型和赋给这个接口的实体类型;字段 data 则指向接口具体的值,一般是一个指向堆内存的指针。
1 | type itab struct { |
如上所述,itab 中有 5 个字段:
-
inter:描述了接口的类型,它包装了_type;还有一个表示接口定义的方法列表的的mhdr字段,以及pkgpath记录定义了接口的包名; -
_type:指得是赋给接口的变量的类型; -
hash:等同于_type中的hash字段,用于类型转换; -
fun: 保存一个函数指针,它指向的是具体类型的函数方法。虽然这里只有一个函数指针,但是它可以调用很多方法。在这个指针对应内存地址的后面依次存储了多个方法,利用指针偏移便可以找到它们;
由于 Go 语言是强类型语言,编译时对每个变量的类型信息做强校验,所以每个类型的元信息要用一个结构体描述。再者 Go 的反射也是基于类型的元信息实现的,_type 就是所有类型最原始的元信息。
1 | type _type struct { |
str 和 ptrToThis,对应的类型是 nameoff 和 typeOff,这两个字段的值是在链接器段合并和符号重定向的时候赋值的。运行时类型名称和具体类型值由 runtime.resolveNameOff 和 runtime.resolveTypeOff 函数计算出来的。
其他的类型,如数组,channel 以及 map 在运行时也是基于 _type 这个元信息表示,它们除了表示类型表自身,还需要表示它的元素的类型,例如:
1 | type maptype struct { |
相比起 iface,eface 就简单多了,它只需一个 _type 字段表示存储的具体值的类型和用于存储实际值的 data。