Go 博客

Go 1.21 中的前向兼容性和工具链管理

Russ Cox
14 August 2023

除了 Go 1.21 对向后兼容性的扩展承诺之外,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 get 命令像更新普通模块要求一样更新 gotoolchain 的要求。例如,如果您正在使用 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

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

go env -w GOTOOLCHAIN=go1.21.1+auto

您再也不必手动下载和安装 Go 工具链了。go 命令会为您处理好一切。

有关更多详细信息,请参阅“Go 工具链”。

下一篇文章:使用 slog 进行结构化日志记录
上一篇文章:向后兼容性、Go 1.21 和 Go 2
博客索引