Golang 泛型初探

Golang 的泛型实现已经正式合并到 master 分支上啦,之后也会在 master 分支上进行开发,那么作为期待这个 feature 许久的 gopher,也想第一时间看看到底是如何实现的。

语法

这里不过多讲解泛型的语法,具体可以参考一下 https://github.com/golang/go/issues/43651 这个 issue。

简单来说,在 struct 和 func 的名字后面可以加一个 [] 里面包含泛型的名字和限制条件,比如:

1
2
3
type container[T any] struct{
elem T
}

any 是个特殊的关键字,表示所有类型都可以。

示例程序

这里我们写一个示例程序来编译成汇编,来看看泛型到底是怎么实现的:

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

type Stringer interface {
String() string
}

type Stringer2 interface {
Stringer
}

type container[T Stringer] struct {
s T
}

type stringerImpl struct {
s string
}

func (s stringerImpl) String() string {
return s.s
}

func loop[T any](s []T) {
for _, v := range s {
_ = v
}
}

func main() {
loop([]int{1, 2, 3, 4, 5})

c := container[Stringer2]{}
loop([]container[Stringer2]{c})
}

编译成汇编

我们先基于 master 分支来编译一个 go 出来,然后用这个 go 来执行以下命令:

1
$ go build -gcflags="-G=3 -l -S" main.go > main.s 2>&1

接下来去main.s这个文件看看,就会发现有这么一段代码:

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
"".#loop[int] STEXT nosplit size=18 args=0x18 locals=0x0 funcid=0x0
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) TEXT "".#loop[int](SB), NOSPLIT|ABIInternal, $0-24
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:39) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:33) MOVQ "".s+16(SP), AX
0x0005 00005 (/Users/purewhite/go/src/local/study/main.go:33) XORL CX, CX
0x0007 00007 (/Users/purewhite/go/src/local/study/main.go:33) JMP 12
0x0009 00009 (/Users/purewhite/go/src/local/study/main.go:33) INCQ CX
0x000c 00012 (/Users/purewhite/go/src/local/study/main.go:33) CMPQ AX, CX
0x000f 00015 (/Users/purewhite/go/src/local/study/main.go:33) JGT 9
0x0011 00017 (/Users/purewhite/go/src/local/study/main.go:33) RET
0x0000 48 8b 44 24 10 31 c9 eb 03 48 ff c1 48 39 c8 7f H.D$.1...H..H9..
0x0010 f8 c3 ..
"".#loop[container[Stringer2]] STEXT nosplit size=21 args=0x18 locals=0x0 funcid=0x0
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) TEXT "".#loop[container[Stringer2]](SB), NOSPLIT|ABIInternal, $0-24
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:44) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0000 00000 (/Users/purewhite/go/src/local/study/main.go:33) MOVQ "".s+16(SP), AX
0x0005 00005 (/Users/purewhite/go/src/local/study/main.go:33) TESTQ AX, AX
0x0008 00008 (/Users/purewhite/go/src/local/study/main.go:33) JLE 20
0x000a 00010 (/Users/purewhite/go/src/local/study/main.go:33) XORL CX, CX
0x000c 00012 (/Users/purewhite/go/src/local/study/main.go:33) INCQ CX
0x000f 00015 (/Users/purewhite/go/src/local/study/main.go:33) CMPQ AX, CX
0x0012 00018 (/Users/purewhite/go/src/local/study/main.go:33) JGT 12
0x0014 00020 (/Users/purewhite/go/src/local/study/main.go:33) RET
0x0000 48 8b 44 24 10 48 85 c0 7e 0a 31 c9 48 ff c1 48 H.D$.H..~.1.H..H
0x0010 39 c8 7f f8 c3 9....

再看 main 中调用的地方:

1
2
3
	0x008c 00140 (/Users/purewhite/go/src/local/study/main.go:39)	CALL	"".#loop[int](SB)
...
0x00c0 00192 (/Users/purewhite/go/src/local/study/main.go:44) CALL "".#loop[container[Stringer2]](SB)

基本可以确定,go 的泛型目前的实现方案是在编译时进行代码生成,这个方案虽然会降低编译速度,但是在运行时是没有性能损耗的。