接口内部实现和判断相等的逻辑
Golang Interface Runtime Implements
底层实现
// $GOROOT/src/runtime/runtime2.go type iface struct { tab *itab data unsafe.Pointer } type eface struct { _type *_type data unsafe.Pointer }在 runtime 内部,interface 实际上有两种表示:
eface用于表示没有方法的空接口( empty interface )类型变量,即interface{}类型的变量。iface用于表示其余拥有方法的接口类型变量。
eface表示的空接口类型没有方法列表,所以第一个字段是一个指向当前存储变量的动态类型信息(_type),而iface除了存储变量的动态类型信息外,还要包含接口本身的信息,所以第一个字段指向的是一个同时包含这两个信息的结构体itab的指针。itab结构体中也有指向当前存储变量的动态类型结构体(_type)的指针。Golang 程序运行时,运行时会为程序中所有的类型都建立只读共享的
_type信息表,所有拥有相同类型的值会指向同一个_type结构体。判断接口是否相等
虽然空接口与非空接口在内部表示上存在差异,可在判断两个 interface 时可以忽略这中结构上的差异,而把所有 interface 的变量内部看作由两部分构成:
(T, V)类型和值。判断是否相等时,只有(T, V)都相等时才相等。比如下面这个例子:type MyErr struct { error } func returnErr() error { var e *MyErr = (*MyErr)(nil) return e } func main() { e := returnErr() fmt.Println(e) fmt.Println(e == nil) }运行结果为:
<nil> false原因在于,当我们申明
var e *MyErr,我们实际上同时将*MyErr的类型信息放入e中(T=*MyErr, V=nil)。所以在main中和nil比较时相当于(T=*MyErr, V=nil) == (T=nil, V=nil)结果就是不相等。要解决这个问题,我们可以在
returnErr中选择直接返回return nil就行。另外还要注意,这里 interface 和 struct 的不同,interface 在只是声明没有存值的情况下其内部值相当于(T=nil, V=nil),而 struct 在声明之后就带上了类型属性,再转换为 interface 时类型就不再为nil。所以下面这样的
returnErr的返回值可以正常判nil:func returnErr() (e error) { return } func returnErr() error { var e error return e }Reference
指向原始笔记的链接
- Golang FAQ
- 《Go 语言精进之路:从新手到高手的编程思想、方法和技巧1》,白明,26.2 接口类型变量的内部表示。
接口的装箱操作
装箱(Boxing)是编程语言领域的一个基础概念,一般指把值类型转换为引用类型。Golang 中把实际类型转化为接口类型可以看作装箱操作,通过调用 runtime/iface.go 中的 convT 实现,基本逻辑就是读取传入类型的类型信息,再分配内存将原有的数据信息复制过去,所有这里是有性能的损耗的,Golang 也为很多内置的类型都做了优化。
Golang 尽量定义小接口
Golang 中推荐定义方法数量一般不超过 3 的小接口,因为小接口有这几个优势。
- 接口越小,抽象程度越高,被接纳度越高。
- 易于实现和测试。
- 契约职责单一,易于复用组合。