Go: Interface 接口
1 · Go: Interface 接口#
An interface type defines a
type set.接口类型定义了一个
类型集。 接口类型值,可以存储类型集中任一类型的值,是一种和类型。
- 用作普通类型:在运行时装一个具体值,并动态分发方法;这种接口只能是
方法集; - 用作泛型参数约束:在编译期约束参数的类型
在没有泛型时,这个类型集,可以看作一组方法的签名,所有满足这些方法的类型,都属于这个类型集。
这是Go接口的优点之一:隐式接口,实现了接口的所有方法的类型就隐式地实现了接口。
有泛型后,接口承担起对类型的约束,这个类型集(多个类型的并集),就是约束了类型的范围。
类型集中可以是具体类型TypeTerm,也可以是底层类型~Type。详细见泛型。
总结:Go 的接口是静态的(编译时检查)和动态的(运行时动态分发)的结合体。
1.1 · 静态、动态
静态:
- 检查某个具体类型是否实现接口
- 检查赋值、传参、返回值是否满足接口
- 作为泛型约束,限制类型参数的类型集合
动态:
- 接口值在运行时保存具体动态类型和值
- 通过接口方法调用发生
动态分发 - 可做
类型断言和type switch查询实际类型
1.2 · 编译期与反射
程序里用到的类型,编译期会生成对应的类型元数据; 运行时当一个值被装进接口时,接口值会带上一个指向该具体类型元数据的指针,以及该值的数据表示;
反射就是在程序里使用这些类型元数据。
1.3 · 运行时底层表示
接口值由两个字组成:
┌─────────────────┬─────────────────┐
│ itable 指针 │ 数据指针 │
└─────────────────┴─────────────────┘
itable 包含:
- 类型元数据
- 函数指针列表
重要特性: itable 对应的是接口类型 + 动态具体类型这一对组合,而不是只对应接口类型。
// runtime.iface
type iface struct {
tab *itab // 重要:包含接口类型、具体类型,还有具体类型对该接口各方法的实现入口
data unsafe.Pointer // 原始数据
}
1.3.1 · 特殊 interface#
对于 interface{} 或 any (无方法),运行时表示里没有 itab,第一字直接保存具体类型指针:
┌──────────────┬──────────────┐
│ 类型指针 │ 数据指针 │
└──────────────┴──────────────┘
// runtime.eface 在 Go 语言中很常见,实现时使用了特殊的类型
type eface struct {
_type *_type // 底层数据类型
data unsafe.Pointer // 底层数据
}
1.3.2 · 设计缺陷?
- 未初始化的接口值是 nil
- 但如果接口值里装的是“具体类型 + 该类型的 nil 值”,那么这个接口值本身不为 nil
接口与具体实现分离, 依赖一个约定好的接口
1.4 · 方法动态派发
调用接口的方法,在编译期间不能确定接口的具体类型(编译器优化:去虚化),那么在运行期间决定调用该方法的哪个实现;
接口类型调用,运行时动态派发;
具体类型调用,编译期确定;使用 -N 关闭编译器优化
获取 itab->fun[0] 到寄存器,iface 的 data 作为接收者参数放到寄存器,通过 CALL 间接调用
2 · links#
3 · issue#
- #21670 函数自动实现同签名单方法接口
- #47487 允许显式转换 I(f)
- #48288 struct 里的函数字段进入 method set