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 的泛型目前的实现方案是在编译时进行代码生成,这个方案虽然会降低编译速度,但是在运行时是没有性能损耗的。