接口内部实现和判断相等的逻辑
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 的小接口,因为小接口有这几个优势。
- 接口越小,抽象程度越高,被接纳度越高。
- 易于实现和测试。
- 契约职责单一,易于复用组合。