Go 部落格

Forward Compatibility and Toolchain Management in Go 1.21

Russ Cox
2023 年 8 月 14 日

除了 1.21 版 Go 擴大承諾向下相容之外,1.21 版 Go 也為 Go 程式碼導入更好的向前相容性,表示 1.21 版 Go 及後續版本會更小心避免編譯錯誤的程式碼,這些程式碼需要更新版本的 Go。具體來說,go.mod 中的 go 行現在會指定最低的 Go 工具鏈版本,而先前的版本則僅為建議,且大多未強制執行。

為了簡化遵循這些要求,1.21 版 Go 也導入工具鏈管理,因此不同的模組可以使用不同的 Go 工具鏈,就像使用不同版本的所需模組一樣。安裝 1.21 版 Go 後,您永遠不必再次手動下載並安裝 Go 工具鏈。go 指令可以為您執行此操作。

本文的其餘部分將詳細描述 Go 1.21 的這兩項變更。

前向相容性

前向相容性是指當 Go 工具鏈嘗試建置為了較新版本 Go 設計的 Go 程式碼時會發生什麼事。如果我的程式相依某個模組 M,並需要 M v1.2.3 新增的錯誤修正,我可以將 require M v1.2.3 加入我的 go.mod,確保我的程式不會編譯為 M 的舊版本。但如果我的程式需要特定版本的 Go,目前還沒辦法表達這個意思:特別是,go.modgo 行沒有表達這個意思。

例如,如果我撰寫使用 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 1.21.1go.mod 的模組中執行 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。

gotoolchain 需求可以使用 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

最後,version+auto 形式的 GOTOOLCHAIN 設定表示預設使用 version,但允許升級到更新版本。如果您已安裝 Go 1.21.0,則在 Go 1.21.1 發布時,您可以透過設定預設的 GOTOOLCHAIN 來變更系統預設

go env -w GOTOOLCHAIN=go1.21.1+auto

您再也不必手動下載和安裝 Go 工具鏈。go 指令會替您處理這件事。

請參閱「Go Toolchains」以取得更多詳細資料。

下一篇文章: 使用 slog 進行結構化記錄
上一篇文章: 後向相容性、Go 1.21 和 Go 2
部落格索引