0%

golang的interface底层实现

在 Go 语言中,interface 和函数一样,都是“第一公民”。interface 可以用在任何使用变量的地方。可以作为结构体内的字段,可以作为函数的形参和返回值,可以作为其他 interface 定义的内嵌字段。

interface 在大型项目中常常用来解耦。在层与层之间用 interface 进行抽象和解耦。由于 Go interface 非侵入的设计,使得抽象出来的代码特别简洁,这也符合 Go 语言设计之初的哲学。

1. interface

interface源码路径: https://github.com/golang/go/blob/master/src/runtime/runtime2.go

1.1 非空接口 iface

1
2
3
4
5
6
7
8
9
10
11
12
type iface struct {
tab *itab // 记录了接口类型信息和实现的方法
data unsafe.Pointer // 指向真正的结构体地址
}

type itab struct {
inter *interfacetype //接口自身的元信息
_type *_type //具体类型的元信息
hash uint32 //_type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
_ [4]byte
fun [1]uintptr //函数指针,指向具体类型所实现的方法
}
  1. 包含两个数据,一个指针指向真正的结构体地址,一个itab 记录了接口类型信息和实现的方法。
  2. fun字段其实是一个动态大小的数组,虽然声明时固定大小为1。使用时会直接通过fun指针获取其中的数据,该数组中保存的元素数量是不确定的。

示例说明:

1
2
3
4
5
6
7
8
type People interface{
Show()
}

func live() People {
var stu *Student
return stu
}

live( )返给上层的是一个People interface{}类型,也就是一个iface struct{}类型。stu为nil,只是iface中的data为nil而已,但是iface struct{}本身并不为nil

Figure-P234_6753

1.2 空接口 eface

空接口eface结构,由两个属性构成,一个是类型信息_type,另一个是数据信息。空接口不需要有方法,所以数据结构更简约。

qq730260-1

data属性:表示指向具体的实例数据的指针,它是一个unsafe.Pointer类型,相当于C语言的一个万能指针void∗。

_type类型: 此属性是Go语言中所有类型的公共描述,Go语言绝大多数的数据结构可以抽象成_type属性,是所有类型的公共描述,type负责决定data应该如何解释和操作,type的结构代码如下:

qq730260-2

函数调用的时候,会先转成eface再传参,所以判断nil永远不会为true。虽然内部的data指向了nil,但是data不等于nil。

2. 类型断言

2.1 相同内部函数可以转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Car interface {
Drive()
}
type TrafficTool interface {
Drive() // 如果再增加一个函数就无法转换
}
type A struct {
}

func (a *A) Drive() {
fmt.Println("a drive")
}

func main() {
var a Car = &A{}
var b = a.(TrafficTool)
a.Drive()
b.Drive()
}
/*
a drive
a drive
*/

2.2 断言

1
2
3
4
5
6
7
8
9
10
func do(v interface{}) {
n := v.(int) // might panic
}

func do(v interface{}) {
n, ok := v.(int)
if !ok {
// 断言失败处理
}
}

这个过程体现在下面的几个函数上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
func assertI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{"", "", inter.typ.string(), ""})
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
tab := i.tab
if tab == nil {
return
}
if tab.inter != inter {
tab = getitab(inter, tab._type, true)
if tab == nil {
return
}
}
r.tab = tab
r.data = i.data
b = true
return
}

// 类似
func assertE2I(inter *interfacetype, e eface) (r iface)
func assertE2I2(inter *interfacetype, e eface) (r iface, b bool)

3. 头脑风暴

3.1 自动结构体指针方法

如果struct 实现了一个方法,编译的时候,再会偷偷的多实现一个指针struct的方法。

用指针实现了方法,不会增加一个新的。

1
2
3
4
5
6
7
8
9
10
11
type A struct {
}

func (a A) Drive() {
fmt.Println("a drive")
}

// 即使不写,编译的时候也会偷偷加上。但是反过来不会。
func (a *A) Drive() {
fmt.Println("a drive")
}

3.2 nil 空接口 空结构体

  • nil 是空,是6种类型的零值(pointer,channel,fuc,interface,map,slice)。
  • nil 不等于 nil。
  • nil 不等于 空 struct。
  • 空 interface没赋值时等于 nil。赋值为nil了,这时候就不等于nil了(因为有了类型信息)。

3.3 总结

  • interface 分为两种,空接口 eface 和 非空接口 iface。
  • 参数interface之所以不等于nil,是因为内部的data指向了nil,但是data不等于nil。
  • 两个底层实现都有一个 _type 结构,进行断言。

4. 参考资料

可以加首页作者微信,咨询相关问题!