目前,泛型方案尚未正式发布,此文章中的内容均基于Taylor在8月份的最新proposal以及当前的go master分支。同时笔者也会尽可能持续更新。
目录
自引用的泛型类型(Generic type referring to itself)
Prologue
golang缺失泛型无疑是gophers的心头之痛,很多时候哪怕是Max或者IfElse这样简单的函数,我们都只能在臃肿的代码和类型转换/断言之间二选一。支持泛型的呼声也从09年一直到了今天。从2010年开始,已经设计了数版泛型方案:
// ============2010-06============
type Vector(t) []t
type Lesser(t) interface {
Less(t) bool
}
func Min(a, b t type Lesser(t)) t {
if a.Less(b) {
return a
}
return b
}
// ============2011-03============
gen [T] type Vector []T
gen [T] type Comparer interface {
Compare(T) int
}
gen [T Comparer[T]] type SortableVector []T
gen [T1, T2] (
type Pair struct {
first T1,
second T2,
}
func MakePair(first T1, second T2) Pair {
return &Pair{first, second}
}
)
// ============2013-12============
type [T] Equaler interface {
Equal(T) bool
}
type [T] Lessable T
func [T] (a Lessable[T]) Less(b T) bool {
return a < b
}
// ============2016-09============
const func AsWriterTo(reader gotype) gotype {
switch reader.(type) {
case io.WriterTo:
return reader
default:
type WriterTo struct {
reader
}
func (t *WriterTo) WriteTo(w io.Writer) (n int64, err error) {
return io.Copy(w, t.reader)
}
return WriterTo (type)
}
}
上面的一些设计还是有不完善之处的,直到19年,Taylor提出了合约(Contract)的概念,参见:https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md,这个方案已经相对成熟了
// define contract
contract stringer(T) {
T String() string
}
constract C(T1, T2, T3) {
T1 int, float64
T2 Method(T1) T1
}
// use contracts
type S(type T C) struct {...}
type I(type T C) interface {...}
func F(type T C)(params ...T) T {...}
但是,最后contract的设计还是被放弃。Go团队转向了类型参数(Type Parameter)的设计。Taylor在今年3月份,表示类型参数的设计已经被接受,同时golang预计将在2022年初的Go 1.18版本中支持此功能。目前最新的go分支已支持泛型的编译。
ref: https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
在Go 1.18中使用泛型
编译第一个泛型程序
- 在GitHub上拉取最新的代码,repo: https://github.com/golang/go.git
- 进入src/目录
- 运行`bash ./all.sh`
- 编译完成后,进入到 ../bin目录,就可以得到最新的go文件
➜ base-eg ../../go/bin/go version
go version devel go1.18-50c69cc3a9 Wed Sep 8 06:59:06 2021 +0000 darwin/amd64
下面写一个简单的程序,从获取一个map中,所有的value值为例。或许你看不太懂含义,我们稍后解释。
package main
import (
"fmt"
)
func main() {
example := map[int]int {
1: 2,
2: 4,
3: 6,
}
fmt.Println(MapValues(example))
example2 := map[string]string {
"1" : "one",
"2" : "two",
"3" : "three",
}
fmt.Println(MapValues(example2))
}
func MapValues[T comparable, K any](m map[T]K) []K {
result := make([]K, 0, len(m))
for _, value := range m {
result = append(result, value)
}
return result
}
执行命令go build -gcflags=-lang=go1.18
得到编译产物,运行后,结果为:
➜ base-eg ../../go/bin/go build -gcflags=-lang=go1.18
➜ base-eg ./base-eg
[2 4 6]
[one two three]
编译命令你或许见过其他版本的,我来简单解释一下
- 过去需要手动指定-G=3,编译命令为:
go build -gcflags=-G=3
- 在8月23号的一个commit中,-G=3默认添加:343732: cmd/compile: enable -G=3 by default ,此时你可以通过
go build
直接构建- 但是这个引入了一些问题,所以26号又添加了一个commit:344871: cmd/compile: always accept 1.18 syntax but complain if not 1.18在检测到go版本低于1.18时禁用泛型编译,所以需要手动指定版本为1.18
使用Goland
实际上,GoLand也已初步支持了泛型,不过这个我没鼓捣成功,博客用的是Goland 20.3,可能是因为我的Goland 20.2不够吧。
可以参考:
使用gopls
适用于vscode等支持LSP的,以vscode为例
- 在settings.json中,将goroot指定为最新的go master分支的目录
- 重启vs code
- 然后就会提示你,goroot变更,一些工具需要重新编译,然后点击确定,等待之后,就可以支持泛型语法了
- 如果不行,go get 一下gopls的master
初探基本概念
类型参数(Type parameters)
如果说函数是传入数值来复用代码,那么泛型便是传入类型来复用代码。Go团队决定允许在函数以及类型定义时,传入类型参数来支持泛型。
泛型函数示例:
func GenericPrintSlice[T any] (arr []T) {
for _, ele := range(arr) {
fmt.Println(ele)
}
}
GenericPrintSlice[int]([]int{1, 2, 3})
泛型类型(Generic Types)示例:
type Vector[T any] []T
var intArray Vector[int]
var stringArray Vector[string]
我们可以注意到如下几点
- 类型参数使用方括号传入&#