Go语言中的接口

golang interface

Posted by alovn on February 10, 2021

空接口

空接口类型interface{}可以接收任意类型的数据,它只要记录这个数据在哪,是什么类型的就足够了。

1
2
3
4
5
6
// src/runtime/runtime2.go#L207
// empty interface,不包含任何方法的空接口
type eface struct {
    _type *_type //指向接口的动态类型元数据
    data  unsafe.Pointer //指向接口的动态值
}

一个空接口的变量在它被赋值以前,_type 和 data 都为 nil。

1
2
3
var e interface{}
f, _ := os.Open("hello.txt")
e = f

把f赋值给e,因为f本身就是个os.File指针,所以data就等于f,而 _type 就指向os.File类型元数据。这就是空接口类型赋值前后的变化。

非空接口

非空接口就是有方法列表的接口类型。一个变量要想赋值给一个非空接口类型,必须要要实现该接口要求的所有方法才行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/runtime/runtime2.go#L202
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype //指向interface的类型元数据
    _type *_type //指向接口的动态类型元数据
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr //记录的是这个动态类型实现的那些接口要求方法地址 variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod //记录接口要求的方法列表
}

与空接口一样这个data字段指向接口的动态值类型,接口要求的方法列表以及接口动态类型信息存储在itab结构体里。

类型断言

一、空接口.(具体类型)

空接口.(具体类型):通过判断 _type 指向的类型元数据是否一致。

例一:

1
2
3
4
var e interface{}
f, _ := os.Open("hello.txt")
e = f
r, ok = e.(*os.File)

这里e的动态类型_type 就是*os.File,所以断言成功。

例二:

1
2
3
4
var e interface{}
f := "hello"
e = f
r, ok = e.(*os.File)

如果像这样赋值, e 的动态类型_type就是 string类型元数据,类型断言就会失败。ok为false, r会赋值为 *os.File 的类型零值 nil。

二、非空接口.(具体类型)

例一:

1
2
3
4
var rw io.ReadWriter
f, _ := os.Open("hello.txt")
rw = f
r, ok = rw.(*os.File)

如果rw这样赋值,它的动态类型就是 *os.File。

例二:

1
2
3
4
var rw io.ReadWriter
f := myType{name: "hello"}
rw = f
r, ok = rw.(*os.File)

如果rw的动态类型不是 *os.File,这时 rw的动态类型就指向了自定义类型 myType 的元数据类型,所以类型断言就会失败。

三、空接口.(非空接口)

1
2
3
4
var e interface{}
// f, _ := os.Open("hello.txt")
// e = f
rw, ok := e.(io.ReadWriter)

这里e.(io.ReadWriter)是要判断e的动态类型是否实现了io.ReadWriter接口。

四、非空接口.(非空接口)

1
2
var w io.Writer
rw, ok := w.(io.ReadWriter)

这里w.(io.ReadWriter)是要判断w存储的动态类型是否实现了io.ReadWriter接口。w是io.Writer类型,接口要求一个Write方法。而io.ReadWriter接口要求实现Read和Write两个方法。

1
2
3
4
var w io.Writer
f, _ := os.Open("hello.txt")
w = f
rw, ok := w.(io.ReadWriter)

如果w像这样赋值,它的动态值类型指向 *os.File,就需要确定 *os.File是否实现了io.ReadWriter接口。

所以类型断言的关键是明确接口的动态类型以及对应的类型实现了哪些方法,而明确这些的关键还是类型元数据以及空接口和非空接口的数据结构。