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
部落格索引