原创分享 一文告诉你如何抢先体验 Go 泛型

bigwhite-github · 2020年12月01日 · 115 次阅读

img{512x368}

本文首发于我主持的“Gopher 部落” 知识星球,欢迎大家加入星球,一起学习 Go 语言!年底前 8.8 折优惠,不要错过哦!

2020年11月22日,Go 核心开发团队技术负责人Russ Coxgolang-dev 论坛上确认了 Go 泛型将在 Go 1.18 落地 (2022.2):

img{512x368}

这对于那些迫切期盼 go 加入泛型的 gopher 来说无疑是一个重大利好消息!不过,泛型是把双刃剑!泛型的加入势必会让 Go 语言的复杂性大幅提升。我很是担心 Go 加入泛型后会像 C++ 模板那样被 “滥用” 而形成很多奇技淫巧,这显然不是 Go 项目组想看到的。因此他们现在在宣传泛型时都是比较谨慎的。Robert GriesemerGopherCon 2020大会上演讲“Typing [Generic] Go”中明确给出了 Go 泛型的使用时机:

  • 可增强静态类型安全性的时候
  • 可以更高效的使用内存的时候
  • 可以 (显著的) 提升性能的时候

虽然这不能完全避免滥用,但至少表明了 Go 团队对泛型使用的态度。“能力越大,责任越大”,大家在使用泛型时务必三思而后行

现在,Go 泛型已经处于 “箭在弦上不得不发” 的状态,作为 Gopher,我们能做的就是拥抱它!

离 Go 1.18 发布还有一年多的时间,对于极其渴望支持泛型的 gopher 来说,这个时间有点长!好在 Go 项目组已经提供了一些抢先体验 Go 泛型语法的方法,这里我们就来全面介绍一下,小伙伴们可以根据自己的情况任选一种抢先体验 Go 泛型!

1. Go 泛型在线 playground

2020.6 月末,Ian Lance Taylor 和 Robert Griesemer 在 Go 官方博客发表了文章《The Next Step for Generics》,介绍了 Go 泛型工作的最新进展。同时,Go 团队还推出了可以在线试验 Go 泛型语法的 playgroundhttps://go2goplay.golang.org

img{512x368}

通过该在线 playground,我们可以体验最新的 Go 泛型语法并查看编译和运行结果。

在线 playground 的好处就在于可以随时随地访问和体验,体验设备也不局限于计算机,甚至可以使用手机/平板终端。不过该 playground 在国内访问不畅,并且体验仅局限于单文件的形式,对于复杂一些的项目无法支持。

2. 基于源码编译出 go2go 工具

Go 项目在dev.go2go分支上加入了 Go 泛型语法的实现,我们可以在本地基于 Go 项目源码构建出可以用于体验 Go 泛型的 go2go 工具。

要想构建 go2go 工具,我们首先就需要下载 Go 项目源码。但截至目前,Go 项目仓库github.com/golang/go有 45000 多次提交,在国内以 20k/s 的速度 clone 这个仓库那是相当耗时费力,还不一定有好结果(经常断连,一断连,就要重新来过)。当然如果你有高速 vpn 则另当别论了。这里介绍一个下载 github 上 Go 项目源码的过渡方法:利用 gitee(码云) 建立 Go 仓库镜像库,然后从码云以 2M/s 速度下载。步骤如下:

  • 在 gitee 上建立一个公共仓库,比如:gitee.com/bigwhite/go,在建立仓库时选择 “导入现有库”,填入现有库的地址:https:///github.com/golang.go.git,之后,强大的 “码云” 就会帮助我们快速同步了。

  • 之后我们就可以从码云 clone 这个仓库:gitee.com/bigwhite/go,2M/s 的速度,一分钟内就完成 clone。并且码云支持强制从源仓库 github.com/golang/go 同步最新更新到镜像仓库,十分方便。

$git clone https://gitee.com/bigwhite/go.git

既然我已经在码云建立的 go 仓库的镜像,各位小伙伴儿们就可以直接 clone 我的公共库 (https://gitee.com/bigwhite/gogo 仓库源码了。) 来获取

接下来,我们来构建 go2go 工具,主要步骤如下 (当前环境为 ubuntu,并已安装的 go 的版本为go 1.15.4 linux/amd64):

  • 切换到 dev.go2go 分支
// 进入下载后的go仓库源码目录(我这里为~/.bin/go)
$git checkout dev.go2go
Branch 'dev.go2go' set up to track remote branch 'dev.go2go' from 'origin'.
Switched to a new branch 'dev.go2go'

注:ubuntu 需安装build-essential(apt-get install build-essential),否则在 go 源码编译过程可能会出现 “fatal error: stdlib.h: No such file or directory” 的错误。

  • 编译 dev.go2go 分支源码

Go 源码编译是 “一键式” 的,并且速度非常快!进入到 Go 项目源码下的 src 目录 (cd ~/.bin/go/src),执行下面命令:

$./all.bash 

Building Go cmd/dist using /root/.bin/go1.15.4. (go1.15.4 linux/amd64)
Building Go toolchain1 using /root/.bin/go1.15.4.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building packages and commands for linux/amd64.
... ...
ALL TESTS PASSED
---
Installed Go for linux/amd64 in /root/.bin/go
Installed commands in /root/.bin/go/bin
*** You need to add /root/.bin/go/bin to your PATH.

构建后的可执行文件 go 与 gofmt 被放在了 bin 目录下 (~/go/bin),为方便使用,我们最好将其所在路径配置到PATH环境变量中。。

  • 验证构建结果
$go version
go version devel +440f144a10 Tue Nov 24 01:29:01 2020 +0000 linux/amd64

如果看到上面结果,说明构建是 ok 的。

接下来,我们就来使用构建出的 go 工具体验一下编译运行一个使用泛型语法编写的源文件sort.go2

// sort.go2

package main

import (
    "fmt"
    "sort"
)

type Lang struct {
    Name string
    Rank int
}

type sliceFn[T any] struct {
    s   []T
    cmp func(T, T) bool
}

func (s sliceFn[T]) Len() int           { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.cmp(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int)      { s.s[i], s.s[j] = s.s[j], s.s[i] }

func SliceFn[T any](s []T, cmp func(T, T) bool) {
    sort.Sort(sliceFn[T]{s, cmp})
}

func main() {
    langs := []Lang{
        {"rust", 2},
        {"go", 1},
        {"swift", 3},
    }

    SliceFn(langs, func(p1, p2 Lang) bool { return p1.Rank < p2.Rank })
    fmt.Println(langs) // [{go 1} {rust 2} {swift 3}]
}

go2go 是以 go tool 的一个子命令形式存在的,它支持编译和运行以.go2为后缀的 Go 源文件,如果让它编译和运行.go文件,它会报如下错误:

$go tool go2go run sort.go
Go file sort.go was not created by go2go

编译运行上面的 sort.go2 的命令和结果如下:

$go tool go2go run sort.go2
[{go 1} {rust 2} {swift 3}]

有小伙伴可能会说,这个例子也是单一源文件,太简单!那我们接下来就整一个稍复杂些的。go2go 这个子命令自带了一些复杂的 Go 泛型包,这些包的源码被放在了 Go 仓库源码的src/cmd/go2go/testdata下面:

$tree -LF 2 go2path
go2path
└── src/
    ├── alg/
    ├── chans/
    ├── constraints/
    ├── graph/
    ├── gsort/
    ├── list/
    ├── maps/
    ├── metrics/
    ├── orderedmap/
    ├── sets/
    └── slices/

go2go 目前仅支持 gopath mode,还不支持module-ware mode。go2go 支持专用的 GO2PATH 环境变量用于指示 GOPATH 路径,也可以用传统的 GOPATH 环境变量。为了使用 go2go 自带的那些样例源码包,我们需要将 GOPATH 或 GO2PATH 设置为\$GOROOT/src/cmd/go2go/testdata/go2path。我们在 go2path 路径下建立我们的样例 repo:

$tree -LF 5 go2path
go2path
└── src/
    │   ... ...
    ├── github.com/
    │   └── bigwhite/
    │       └── gsort-demo/
    │           └── demo.go2
    │   ... ...        
    └── slices/
        ├── slices.go2
        └── slices_test.go2

// demo.go2
package main

import (
    "fmt"
    "gsort"
)

type Lang struct {
    Name string
    Rank int
}

func main() {
    langs := []Lang{
        {"rust", 2},
        {"go", 1},
        {"swift", 3},
    }

    gsort.SliceFn(langs, func(p1, p2 Lang) bool { return p1.Rank < p2.Rank })
    fmt.Println(langs)
}

我们可以用两种方法运行 demo.go2:

// 设置GO2PATH:

~/.bin/go/src/cmd/go2go/testdata/go2path/src/github.com/bigwhite/gsort-demo$ GO2PATH=$GOROOT/src/cmd/go2go/testdata/go2path go tool go2go run demo.go2
[{go 1} {rust 2} {swift 3}]

或

// 设置GOPATH和关闭GO111MODULE:

~/.bin/go/src/cmd/go2go/testdata/go2path/src/github.com/bigwhite/gsort-demo$ GOPATH=$GOROOT/src/cmd/go2go/testdata/go2path GO111MODULE=off go tool go2go run demo.go2 
[{go 1} {rust 2} {swift 3}]

通过源码构建 go2go 工具的方法是体验 Go 泛型最基本的方法,我们还可以定期更新 Go 项目源码以体验泛型草案的最新变化。我们还可以通过go doc cmd/go2go来查看 go2go 命令的文档。

3. 使用 go2go 的 docker 容器

如果觉得使用源码构建本地可用的 go2go 工具依然 “门槛高” 或者繁琐,那么可以利用一些 gopher 已经上传的现成的 docker 容器来构建使用了泛型语法的 *.go2 文件。这里使用的是levonet/golang:go2go

$docker pull levonet/golang:go2go
  • 使用 go2go 容器编译运行单个 *.go2 文件

我们还以上面那个 sort.go2 为例,该文件可以放在任意目录下,然后我们在该文件所在目录下执行下面命令即可编译运行它:

$ docker run --rm -v "$PWD":/go/src/myapp -w /go/src/myapp levonet/golang:go2go go tool go2go run sort.go2
[{go 1} {rust 2} {swift 3}]

这句 docker run 命令的含义是:将宿主机当前工作目录 (即 sort.go2 所在目录) 挂载到容器中的/go/src/myapp下面,并将/go/src/myapp作为当前工作目录,执行go tool go2go run sort.go2

对于复杂的如上面的 github.com/bigwhite/gsort-demo 的例子,通过 docker 容器一样可以编译,只不过命令复杂一些:

~/temp/github.com $docker run --rm -v "$PWD":/usr/local/lib/go/src/cmd/go2go/testdata/go2path/src/github.com -w /usr/local/lib/go/src/cmd/go2go/testdata/go2path/src/github.com/bigwhite/gsort-demo -e GO2PATH="/usr/local/lib/go/src/cmd/go2go/testdata/go2path" levonet/golang:go2go go tool go2go run demo.go2
[{go 1} {rust 2} {swift 3}]

我们将 github.com 目录放在任意目录下,比如:~/temp,然后将当前目录挂载到容器的/usr/local/lib/go/src/cmd/go2go/testdata/go2path/src/github.com目录下,设定工作目录为/usr/local/lib/go/src/cmd/go2go/testdata/go2path/src/github.com/bigwhite/gsort-demo,然后为容器新增以环境变量GO2PATH,这样我们就可以编译运行 demo.go2 了。

注 1:容器中的 GOROOT 为/usr/local/lib/go

4. 使用 Goland 体验 Go 泛型

著名 Go 语言 IDE 产品 goland 也宣布支持体验最新的 Go 泛型语法,由于笔者很少使用图形化的 IDE,因此各位小伙伴可自行通过这篇博客https://blog.jetbrains.com/go/2020/11/24/experimenting-with-go-type-parameters-generics-in-goland/来了解具体情况。

5. 参考资料


我的 Go 技术专栏:“改善 Go 语⾔编程质量的 50 个有效实践” 上线了,欢迎大家订阅学习!

img{512x368}

我的网课 “Kubernetes 实战:高可用集群搭建、配置、运维与应用” 在慕课网热卖中,欢迎小伙伴们订阅学习!

Gopher Daily(Gopher 每日新闻) 归档仓库 - https://github.com/bigwhite/gopherdaily

我的联系方式:

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册