Golang 泛型初探

PureWhiteWu發表於2021-03-09

Golang 的泛型實現已經正式合併到 master 分支上啦,之後也會在 master 分支上進行開發,那麼作為期待這個 feature 許久的 gopher,也想第一時間看看到底是如何實現的。

語法

這裡不過多講解泛型的語法,具體可以參考一下 https://github.com/golang/go/issues/43651 這個 issue。簡單來說,在 struct 和 func 的名字後面可以加一個 [] 裡面包含泛型的名字和限制條件,比如:

type container[T any] struct{
    elem T
}

any 是個特殊的關鍵字,表示所有型別都可以。

示例程式

這裡我們寫一個示例程式來編譯成彙編,來看看泛型到底是怎麼實現的:

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 來執行以下命令:

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

接下來去main.s這個檔案看看,就會發現有這麼一段程式碼:

"".#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 中呼叫的地方:

  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 的泛型目前的實現方案是在編譯時進行程式碼生成,這個方案雖然會降低編譯速度,但是在執行時是沒有效能損耗的。

更多原創文章乾貨分享,請關注公眾號
  • Golang 泛型初探
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章