iface和eface

Go中描述接口的底层结构体为iface和eface,iface描述的接口包含方法,而eface不包含方法,为空接口:interface{}

iface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type iface struct {
tab *itab
data unsafe.Pointer
}

type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}

看一下iface结构体的源码,可以发现由两个属性构成,其中data保存了指向接口具体的值的指针,而tab指向一个itab实体,表示接口的类型赋给这个接口的实体类型。

在itab结构体之中,_type字段描述了实体的类型inter字段表述了接口的类型,fun字段放置和接口方法对应的具体数据类型的方法地址,一般每次给接口赋值发生转换的时候会更新此表,因为新的接口实体对应的方法地址和旧的不一样,或者直接拿缓存的itab。关于fun字段的类型,只是放的是第一个方法的函数指针,后面多的直接增加地址就好了。另外,这些方法是按照函数名称的字典序进行排序的。

1
2
3
4
5
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}

关于interfacetype结构体,它描述的是接口的类型

其中mhdr放的是接口所定义的的函数列表,而pkgpath记录定义的接口的包名。

iface结构体全貌

eface

1
2
3
4
type eface struct {
_type *_type
data unsafe.Pointer
}

eface就很简单,一个data和前面的一样,一个_type放了接口实体的类型。

接口的动态类型和动态值

iface的tab字段是动态类型,data是动态值。

接口值的零值是指动态类型动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil

例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
28
package main

import "fmt"

type Coder interface {
code()
}

type Gopher struct {
name string
}

func (g Gopher) code() {
fmt.Printf("%s is coding\n", g.name)
}

func main() {
var c Coder
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)

var g *Gopher
fmt.Println(g == nil)

c = g
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
}

输出:

1
2
3
4
5
true
c: <nil>, <nil>
true
false
c: *main.Gopher, <nil>

例2

这里有个很有意思的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type MyError struct {}

func (i MyError) Error() string {
return "MyError"
}

func main() {
err := Process()
fmt.Println(err)

fmt.Println(err == nil)
}

func Process() error {
var err *MyError = nil
return err
}

输出

1
2
<nil>
false

可以发现虽然这个err动态值为nil,但是他的动态类型被赋予了MyError,所以这个err总体不等于nil。

接口断言

在知道了接口底层之后,可以很容易理解接口断言的原理了。

只要把iface中的itab里面_type对比一下断言的类型就好了。

接口转换

把一个接口变量赋值给另一个接口变量

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
package main

import "fmt"

type coder interface {
code()
run()
}

type runner interface {
run()
}

type Gopher struct {
language string
}

func (g Gopher) code() {
return
}

func (g Gopher) run() {
return
}

func main() {
var c coder = Gopher{}

var r runner
r = c
fmt.Println(c, r)
}

我们看下是怎么实现转换的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func convI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}

这里就是把r的data赋值为原先c的data,把r的tab里面的type变成c的type,因为底层实体的地址不变嘛。

然后r的inter要用传进来的这个inter,也就是例子中的runner的inter,这样就成功转换了。