原文在这里

由 Russ Cox 发布于 2023年8月14日

在 Go 1.21 中,除了对向后兼容性的扩展承诺外,还引入了对 Go 代码的更好的向前兼容性,这意味着 Go 1.21 及以后的版本将更好地处理不会误编译需要更新版本 Go 的代码的情况。具体来说,go.mod 中的 go 行现在指定了最小所需的 Go 工具链版本,而在以前的版本中,它主要是一个未强制执行的建议。

为了更容易地跟上这些要求,Go 1.21 还引入了工具链管理,因此不同的模块可以使用不同的 Go 工具链,就像它们可以使用所需模块的不同版本一样。安装 Go 1.21 后,你再也不需要手动下载和安装 Go 工具链了。go 命令可以为你做到这一点。

本文的其余部分将详细介绍这两个 Go 1.21 的变化。

向前兼容性

向前兼容性是指当 Go 工具链试图构建为更新版本的 Go 设计的 Go 代码时会发生什么。如果我的程序依赖于模块 M 并需要在 M v1.2.3 中添加的错误修复,我可以在我的 go.mod 中添加 require M v1.2.3,保证我的程序不会针对 M 的旧版本进行编译。但是如果我的程序需要特定版本的 Go,到目前为止还没有任何方法可以表达这一点:特别是,go.mod 中的 go 行并没有表达这一点。

例如,如果我编写的代码使用了在 Go 1.18 中添加的新的泛型,我可以在我的 go.mod 文件中写 go 1.18,但这并不会阻止早期版本的 Go 尝试编译代码,产生如下错误:

$ cat go.mod
go 1.18
module example

$ go version
go version go1.17

$ go build
# example
./x.go:2:6: missing function body
./x.go:2:7: syntax error: unexpected [, expecting (
note: module requires Go 1.18
$

这两个编译器错误是误导性的噪音。真正的问题是由 go 命令作为提示打印的:程序编译失败,所以 go 命令指出了潜在的版本不匹配。

在这个例子中,我们很幸运构建失败了。如果我编写的代码只在 Go 1.19 或更高版本中运行正确,因为它依赖于该补丁版本中修复的一个错误,但我在代码中没有使用任何 Go 1.19 特定的语言特性或包,早期版本的 Go 将编译它并静默成功。

从 Go 1.21 开始,Go 工具链将把 go.mod 中的 go 行视为不是指南而是规则,该行可以列出特定的点版本或候选版本。也就是说,Go 1.21.0 知道它甚至不能构建在其 go.mod 文件中说 go 1.21.1 的代码,更不用说说像 go 1.22.0 这样的更晚版本的代码了。

我们允许旧版本的 Go 尝试编译新代码的主要原因是为了避免不必要的构建失败。被告知你的 Go 版本太旧以至于无法构建程序是非常令人沮丧的,特别是如果它可能无论如何都能工作(也许要求过于保守),特别是当更新到新的 Go 版本是一种麻烦的时候。为了减少强制执行 go 行作为要求的影响,Go 1.21 在核心分发中添加了工具链管理。

工具链管理

当你需要一个新版本的 Go 模块时,go 命令会为你下载它。从 Go 1.21 开始,当你需要一个新的 Go 工具链时,go 命令也会为你下载。这个功能就像 Node 的 nvm 或 Rust 的 rustup,但是它是内置在核心 go 命令中的,而不是一个单独的工具。

如果你正在运行 Go 1.21.0,并且在一个 go.mod 说 go 1.21.1 的模块中运行 go 命令,比如,go build,Go 1.21.0 的 go 命令会注意到你需要 Go 1.21.1,下载它,并重新调用该版本的 go 命令来完成构建。当 go 命令下载并运行这些其他工具链时,它不会在你的 PATH 中安装它们或覆盖当前的安装。相反,它将它们作为 Go 模块下载,继承所有模块的安全性和隐私权益,并从模块缓存中运行它们。

go.mod 中还有一个新的 toolchain 行,它指定了在特定模块中工作时使用的最小 Go 工具链。与 go 行不同,toolchain 不对其他模块施加要求。例如,go.mod 可能会说:

module m
go 1.21.0
toolchain go1.21.4

这表示其他需要 m 的模块需要提供至少 Go 1.21.0,但是当我们自己在 m 中工作时,我们希望一个更新的工具链,至少 Go 1.21.4。

go 和 toolchain 的要求可以像普通的模块要求一样使用 go get 进行更新。例如,如果你正在使用 Go 1.21 的候选版本之一,你可以通过运行以下命令在特定模块中开始使用 Go 1.21.0:

go get go@1.21.0

这将下载并运行 Go 1.21.0 来更新 go 行,未来的 go 命令调用将看到行 go 1.21.0 并自动重新调用该版本。

或者,如果你想在模块中开始使用 Go 1.21.0,但是将 go 行设置为旧版本,以帮助维护与早期版本 Go 用户的兼容性,你可以更新 toolchain 行:

go get toolchain@go1.21.0

如果你想知道在特定模块中运行的 Go 版本是什么,答案与以前相同:运行 go version。

你可以使用 GOTOOLCHAIN 环境变量强制使用特定的 Go 工具链版本。例如,要使用 Go 1.20.4 测试代码:

GOTOOLCHAIN=go1.20.4 go test

最后,GOTOOLCHAIN 设置为 version+auto 的形式意味着默认使用 version,但允许升级到更新的版本。如果你已经安装了 Go 1.21.0,那么当 Go 1.21.1 发布时,你可以通过设置默认的 GOTOOLCHAIN 来更改你的系统默认设置:

go env -w GOTOOLCHAIN=go1.21.1+auto

你再也不需要手动下载和安装 Go 工具链了。go 命令会为你处理。

有关更多详细信息,请参见Go 工具链


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意