go interface 设计与实现

在上一篇文章《go interface 基本用法》中,我们了解了 go 中 interface 的一些基本用法,其中提到过 接口本质是一种自定义类型,本文就来详细说说为什么说 接口本质是一种自定义类型,以及这种自定义类型是如何构建起 go 的 interface 系统的。

本文使用的源码版本: go 1.19。另外本文中提到的 interface接口 是同一个东西。

前言

在了解 go interface 的设计过程中,看了不少资料,但是大多数资料都有生成汇编的操作,但是在我的电脑上指向生成汇编的操作的时候,
生成的汇编代码却不太一样,所以有很多的东西无法验证正确性,这部分内容不会出现在本文中。本文只写那些经过本机验证正确的内容,但也不用担心,因为涵盖了 go interface 设计与实现的核心部分内容,但由于水平有限,所以只能尽可能地传达我所知道的关于 interface 的一切东西。对于有疑问的部分,有兴趣的读者可以自行探索。

如果想详细地了解,建议还是去看看 iface.go,里面有接口实现的一些关键的细节。但是还是有一些东西被隐藏了起来,
导致我们无法知道我们 go 代码会是 iface.go 里面的哪一段代码实现的。

接口是什么?

接口(interface)本质上是一种结构体。

我们先来看看下面的代码:

// main.go
package main

type Flyable interface {
   
	Fly()
}

// go tool compile -N -S -l main.go
func main() {
   
	var f1 interface{
   }
	println(f1) // CALL    runtime.printeface(SB)

	var f2 Flyable
	println(f2) // CALL    runtime.printiface(SB)
}

我们可以通过 go tool compile -N -S -l main.go 命令来生成 main.go 的伪汇编代码,生成的代码会很长,下面省略所有跟本文主题无关的代码:

// main.go:10 => println(f1)
0x0029 00041 (main.go:10)  CALL  runtime.printeface(SB)
// main.go:13 => println(f2)
0x004f 00079 (main.go:13)  CALL  runtime.printiface(SB)

我们从这段汇编代码中可以看到,我们 println(f1) 实际上是对 runtime.printeface 的调用,我们看看这个 printeface 方法:

func printeface(e eface) {
   
	print("(", e._type, ",", e.data, ")")
}

我们看到了,这个 printeface 接收的参数实际上是 eface 类型,而不是 interface{} 类型,我们再来看看 println(f2) 实际调用的 runtime.printiface 方法:

func printiface(i iface) {
   
	print("(", i.tab, ",", i.data, ")")
}

也就是说 interface{} 类型在底层实际上是 eface 类型,而 Flyable 类型在底层实际上是 iface 类型。

这就是本文要讲述的内容,go 中的接口变量其实是用 ifaceeface 这两个结构体来表示的:

  • iface 表示某一个具体的接口(含有方法的接口)。
  • eface 表示一个空接口(interface{}

在这里插入图片描述

iface 和 eface 结构体

ifaceeface 的结构体定义(runtime/iface.go):

// 非空接口(如:io.Reader)
type iface struct {
   
	tab  *itab          // 方法表
	data unsafe.Pointer // 指向变量本身的指针
}

// 空接口(interface{})
type eface struct {
   
	_type *_type         // 接口变量的类型
	data  unsafe.Pointer // 指向变量本身的指针
}

go 底层的类型信息是使用 _type 结构体来存储的。

比如,我们有下面的代码:

package main

type Bird struct {
   
	name string
}

func (b Bird) Fly() {
   
}

type Flyable interface {
   
	Fly()
}

func main() {
   
	bird := Bird{
   name: "b1"}
	var efc interface{
   } = bird // efc 是 eface
	var ifc Flyable = bird // ifc 是 iface

	println(efc) // runtime.printeface
	println(ifc) // runtime.printiface
}

在上面代码中,efceface 类型的变量,对应到 eface 结构体的话,_type 就是 Bird 这个类型本身,而 data 就是 &bird 这个指针:

在这里插入图片描述

类似的,ifciface 类型的变量,对应到 iface 结构体的话,data 也是 &bird 这个指针:

在这里插入图片描述

_type 是什么?

在 go 中,_type 是保存了变量类型的元数据的结构体,定义如下:

// _type 是 go 里面所有类型的一个抽象,里面包含 GC、反射、大小等需要的细节,
// 它也决定了 data 如何解释和操作。
// 里面包含了非常多信息:类型的大小、哈希、对齐及 kind 等信息
type _type struct {
   
    size       uintptr // 数据类型共占用空间的大小
    ptrdata    uintptr // 含有所有指针类型前缀大小
    hash       uint32  // 类型 hash 值;避免在哈希表中计算
    tflag      tflag   // 额外类型信息标志
    align      uint8   // 该类型变量对齐方式
    fieldAlign uint8   // 该类型结构体字段对齐方式
    kind       uint8   // 类型编号
    // 用于比较此类型对象的函数
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gc 相关数据
    gcdata    *byte
    str       nameOff // 类型名字的偏移
    ptrToThis typeOff
}

这个 _type 结构体定义大家随便看看就好了,实际上,go 底层的类型表示也不是上面这个结构体这么简单。

但是,我们需要知道的一点是(与本文有关的信息),通过 _type 我们可以得到结构体里面所包含的方法这些信息。
具体我们可以看 itabinit 方法(runtime/iface.go),我们会看到如下几行:

typ := m._type
x := typ.uncommon() // 结构体类型

nt := int(x.mcount)   // 实际类型的方法数量
// 实际类型的方法数组,数组元素为 method
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]

在底层,go 是通过 _type 里面 uncommon 返回的地址,加上一个偏移量(x.moff)来得到实际结构体类型的方法列表的。

我们可以参考一下下图想象一下:

在这里插入图片描述

itab 是什么?

我们从 iface 中可以看到,它包含了一个 *itab 类型的字段,我们看看这个 itab 的定义:

// 编译器已知的 itab 布局
type itab struct {
   
	inter *interfacetype // 接口类型
	_type *_type
	hash  uint32
	_     [4]byte
	fun   [1]uintptr // 变长数组. fun[0]==0 意味着 _type 没有实现 inter 这个接口
}

// 接口类型
// 对应源代码:type xx interface {}
type interfacetype struct {
   
    typ     _type     // 类型信息
    pkgpath name      // 包路径
    mhdr    []imethod // 接口的方法列表
}

根据 interfacetype 我们可以得到关于接口所有方法的信息。同样的,通过 _type 也可以获取结构体类型的所有方法信息。

从定义上,我们可以看到 itab*interfacetype*_type 有关,但实际上有什么关系从定义上其实不太能看得出来,
但是我们可以看它是怎么被使用的,现在,假设我们有如下代码:

// i 在底层是一个 interfacetype 类型
type i interface {
   
	A()
	C()
}

// t 底层会用 _type 来表示
// t 里面有 A、B、C、D 方法
// 因为实现了 i 中的所有方法,所以 t 实现了接口 i
type t struct {
   }
func (t) A()  {
   }
func (t) B()  {
   }
func
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张无忌打怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值