Go工具鏈版本已不由你定:Go和Toolchain指令詳解
Go語言自誕生以來,就一直將向后兼容性作為其核心理念之一。Go1兼容性承諾[1]確保了為Go1.0編寫的代碼能夠在后續(xù)的Go1.x版本中持續(xù)正確地編譯和運行。這一承諾為Go的成功奠定了堅實的基礎(chǔ),它不僅保障了穩(wěn)定性,也大大減輕了隨著語言演進(jìn)帶來的代碼維護(hù)負(fù)擔(dān)。然而,兼容性的內(nèi)涵并不僅限于向后兼容。向前兼容性,即舊版本的工具鏈能夠優(yōu)雅地處理針對新版本編寫的代碼,對于打造流暢的開發(fā)體驗同樣至關(guān)重要。
在Go 1.21版本[2]之前,向前兼容性在某種程度上是一個被忽視的領(lǐng)域。盡管go.mod文件中的go指令可以標(biāo)明模塊預(yù)期的Go版本,但在實際中,它更像是一個指導(dǎo)性建議,而非強(qiáng)制性規(guī)則。舊版本的Go工具鏈會嘗試編譯那些需要較新版本的代碼,這經(jīng)常導(dǎo)致令人困惑的錯誤,更有甚者會出現(xiàn)“靜默成功”的情況——代碼雖然可以編譯,但由于較新版本中的細(xì)微改動,其運行時行為可能并不正確。
Go 1.21的發(fā)布標(biāo)志著這一現(xiàn)狀的重大轉(zhuǎn)變。該版本引入了健壯且自動化的工具鏈管理機(jī)制,將go指令轉(zhuǎn)變?yōu)橐豁棌?qiáng)制性要求,并簡化了使用不同Go版本進(jìn)行開發(fā)的工作流程。即將發(fā)布的Go 1.24版本在此基礎(chǔ)上進(jìn)一步增強(qiáng),引入了tool指令[3],允許開發(fā)者指定對外部工具及其特定版本的依賴,從而進(jìn)一步提升了代碼的可重復(fù)性和項目的可維護(hù)性。
這些改進(jìn)進(jìn)一步明確和鞏固了go命令作為全方位依賴管理器的角色定位,它不僅管理外部模塊,還負(fù)責(zé)管理Go工具鏈版本,以及越來越多的外部開發(fā)工具(如下圖):
圖片
不過向前兼容性規(guī)則的明確以及toolchain指令的引入也給Go開發(fā)者帶來一定的理解上的復(fù)雜性,并且在使用Go 1.21版本之后,我們可能遇到會遇到一些因Go工具鏈版本選擇而導(dǎo)致的編譯問題。
本文將通過一系列典型場景和詳細(xì)的示例,幫助讀者全面理解Go向前兼容性的規(guī)則,以及go指令以及toolchain指令對Go工具鏈選擇的細(xì)節(jié),從而讓大家能更加自信地駕馭Go開發(fā)中不斷演進(jìn)的技術(shù)環(huán)境。
接下來,我們就從對向前兼容性的理解開始!
1. 理解向前兼容性
向前兼容性,在編程語言的語境中,指的是舊版本的編譯器或運行時環(huán)境能夠處理針對該語言的新版本編寫的代碼。它與向后兼容性相對,后者確保的是新版本的語言能夠處理為舊版本編寫的代碼。向后兼容性對于維護(hù)現(xiàn)有代碼庫至關(guān)重要,而向前兼容性則是在使用不斷演進(jìn)的語言和依賴項時獲得流暢開發(fā)體驗的關(guān)鍵所在。
向前兼容性的挑戰(zhàn)源于新語言版本通常會引入新的特性、語法變更或?qū)?biāo)準(zhǔn)庫的修改。如果舊的工具鏈遇到了依賴于這些新元素的代碼,它可能無法正確地編譯或解釋這些代碼。理想情況下,工具鏈應(yīng)該能夠識別出代碼需要一個更新的版本,并提供清晰的錯誤提示,從而阻止編譯或執(zhí)行。
在Go 1.21之前的版本中,向前兼容性并沒有得到嚴(yán)格的保證。讓我們來看一個例子。我們用Go 1.18泛型語法編寫一個泛型函數(shù)Print:
// toolchain-directive/demo1/mymodule.go
package mymodule
func Print[T any](s T) {
println(s)
}
// toolchain-directive/demo1/go.mod
module mymodule
go 1.18
如果你嘗試使用Go 1.17版本來構(gòu)建這個模塊,你將會遇到類似以下的錯誤:
$go version
go version go1.17 darwin/amd64
$go build
# mymodule
./mymodule.go:3:6: missing function body
./mymodule.go:3:11: syntax error: unexpected [, expecting (
note: module requires Go 1.18
這些錯誤信息具有一定的誤導(dǎo)性,它們指向的是語法錯誤,而不是問題的本質(zhì):這段代碼使用了Go 1.18版本中才引入的泛型特性[4]。雖然go命令確實打印了一條有用的提示(note: module requires Go 1.18),但對于規(guī)模大一些的項目來說,在滿屏的編譯錯誤中,這條提示很容易被忽略。
而比上面這個示例更隱蔽的問題是所謂的“靜默成功”。
設(shè)想這樣一個場景:Go標(biāo)準(zhǔn)庫中的某個bug在Go 1.19版本中被修復(fù)了。你編寫了一段代碼,并在不知情的情況下依賴于這個bug修復(fù)。如果你沒有使用任何Go 1.19版本特有的語言特性,并且你的go.mod文件中指定的是go 1.19,那么舊版本的Go 1.18工具鏈將會毫無怨言地編譯你的代碼并獲得成功。然而,在運行這段代碼時,你的程序可能會表現(xiàn)出不正確的行為,因為那個bug在Go 1.18的標(biāo)準(zhǔn)庫中依然存在。這就是“靜默成功”——編譯過程沒有任何錯誤提示,但最終生成的程序卻是有缺陷的。
在Go 1.21版本之前,go.mod文件中的go指令更多的是一種指導(dǎo)性意見。它表明了期望使用的Go版本,但舊的工具鏈并不會嚴(yán)格執(zhí)行它。這種執(zhí)行上的疏漏是導(dǎo)致Go開發(fā)者面臨向前兼容性挑戰(zhàn)的主要原因。
Go 1.21版本從根本上改變了go指令的處理方式。它不再是一個可有可無的建議,而是一個強(qiáng)制性的規(guī)則。下面我們就來看看Go 1.21及更高版本中是如何確保向前兼容性的。由于多數(shù)情況下,我們不會顯式在go.mod顯式指定toolchain指令,因此,我們先來看看沒有顯式指定toolchain指令時,go指令對向前兼容性的影響。
2. 作為規(guī)則的go指令:確保向前兼容性(Go 1.21及更高版本)
Go 1.21對Go version、language version、release version等做了更明確的定義,我們先來看一下,這對后續(xù)理解go.mod文件中g(shù)o指令的作用很有幫助。下圖形象的展示了各個version之間的關(guān)系:
圖片
Go版本(Go Version),也是發(fā)布版本(Release Version)使用1.N.P的版本號形式,其中1.N稱為語言版本(language version),表示實現(xiàn)該版本Go語言和標(biāo)準(zhǔn)庫的Go版本的整體系列。1.N.P是1.N語言版本的一個實現(xiàn),初始實現(xiàn)是1.N.0,也是1.N的第一次發(fā)布!后續(xù)的1.N.P成為1.N的補(bǔ)丁發(fā)布。
任何兩個Go版本(Go version)都可以進(jìn)行比較,以判斷一個是小于、大于還是等于另一個。
如果語言版本不同,則語言版本的比較結(jié)果決定Go版本的大小。比如:1.21.9 vs. 1.22,前者的語言版本是1.21,后者語言版本是1.22,因此1.21.9 < 1.22。
如果語言版本相同,從小到大的排序為:語言版本本身、按R排序的候選版本(1.NrcR),然后按P排序的發(fā)布版本,例如:
1.21 < 1.21rc1 < 1.21rc2 < 1.21.0 < 1.21.1 < 1.21.2。
在Go 1.21之前,Go初始發(fā)布版本為1.N,而不是1.N.0,因此對于N < 21,排序被調(diào)整為將1.N放在候選版本(rc)之后,例如:
1.20rc1 < 1.20rc2 < 1.20rc3 < 1.20 < 1.20.1。
更早期版本的Go有beta發(fā)布,例如1.18beta2。Beta發(fā)布在版本排序中被放置在候選版本之前,例如:
1.18beta1 < 1.18beta2 < 1.18rc1 < 1.18 < 1.18.1。
有了上述對Go version等的理解,我們再來看看go.mod中g(shù)o指令在向前兼容性規(guī)則中的作用。
Go 1.21及更高版本中,go.mod文件中的go指令聲明了使用模塊或工作空間(workspace)所需的最低Go版本。出于兼容性原因,如果go.mod文件中省略了go指令行(通常我們都不這么做),則該模塊被視為隱式使用go 1.16這個指令行;如果go.work文件中省略了go指令行,則該工作空間被視為隱式使用go 1.18這個指令行。
那么,Go 1.21及更高版本的Go工具鏈在遇到go.mod中g(shù)o指令行中的Go版本高于自身時會怎么做呢?下面我們通過四個場景的示例來看一下。
圖片
- 場景一
當(dāng)前本地工具鏈go 1.22.0,go.mod中g(shù)o指令行為go 1.23.0:
// toolchain-directive/demo2/scene1/go.mod
module scene1
go 1.23.0
執(zhí)行構(gòu)建:
$go build
go: downloading go1.23.0 (darwin/amd64)
... ...
Go自動下載當(dāng)前go module中g(shù)o指令行中的Go工具鏈版本并對當(dāng)前module進(jìn)行構(gòu)建。
- 場景二
當(dāng)前本地工具鏈go 1.22.0,go.mod中g(shù)o指令行為go 1.22.0,但當(dāng)前module依賴的github.com/bigwhite/a的go.mod中g(shù)o指令行為go 1.23.1:
// toolchain-directive/demo2/scene2/go.mod
module scene2
go 1.22.0
require (
github.com/bigwhite/a v1.0.0
)
replace github.com/bigwhite/a => ../a
執(zhí)行構(gòu)建:
$go build
go: module ../a requires go >= 1.23.1 (running go 1.22.0)
Go發(fā)現(xiàn)當(dāng)前go module依賴的go module中g(shù)o指令行中的Go版本比當(dāng)前module的更新,則會輸出錯誤提示!
- 場景三
當(dāng)前本地工具鏈go 1.22.0,go.mod中g(shù)o指令行為go 1.22.0,但當(dāng)前module依賴的github.com/bigwhite/a的go.mod中g(shù)o指令行為go 1.23.1,而依賴的github.com/bigwhite/b的go.mod中g(shù)o指令行為go 1.23.2:
// toolchain-directive/demo2/scene3/go.mod
module scene3
go 1.22.0
require (
github.com/bigwhite/a v1.0.0
github.com/bigwhite/b v1.0.0
)
replace github.com/bigwhite/a => ../a
replace github.com/bigwhite/b => ../b
執(zhí)行構(gòu)建:
$go build
go: module ../b requires go >= 1.23.2 (running go 1.22.0)
Go發(fā)現(xiàn)當(dāng)前go module依賴的go module中g(shù)o指令行中的Go版本比當(dāng)前module的更新,則會輸出錯誤提示!并且選擇了滿足依賴構(gòu)建的最小的Go工具鏈版本。
- 場景四
當(dāng)前本地工具鏈go 1.22.0,go.mod中g(shù)o指令行為go 1.23.0,但當(dāng)前module依賴的github.com/bigwhite/a的go.mod中g(shù)o指令行為go 1.23.1,而依賴的github.com/bigwhite/b的go.mod中g(shù)o指令行為go 1.23.2:
// toolchain-directive/demo2/scene4/go.mod
module scene4
go 1.23.0
require (
github.com/bigwhite/a v1.0.0
github.com/bigwhite/b v1.0.0
)
replace github.com/bigwhite/a => ../a
replace github.com/bigwhite/b => ../b
執(zhí)行構(gòu)建:
$go build
go: downloading go1.23.0 (darwin/amd64)
... ..
Go發(fā)現(xiàn)當(dāng)前go module依賴的go module中g(shù)o指令行中的Go版本與當(dāng)前module的兼容,但比本地Go工具鏈版本更新,則會下載當(dāng)前go module中g(shù)o指令行中的Go版本進(jìn)行構(gòu)建。
從以上場景的執(zhí)行情況來看,只有選擇了當(dāng)前go module的工具鏈版本時,才會繼續(xù)構(gòu)建下去,如果本地找不到這個版本的工具鏈,go會自動下載該版本工具鏈再進(jìn)行編譯(前提是GOTOOLCHAIN=auto)。如果像場景2和場景3那樣,依賴的module的最低Go version大于當(dāng)前module的go version,那么Go會提示錯誤并結(jié)束編譯!后續(xù)你需要顯式指定要使用的工具鏈才能繼續(xù)編譯!以場景3為例,通過GOTOOLCHAIN顯式指定工具鏈,我們可以看到下面結(jié)果:
// demo2/scene3
$GOTOOLCHAIN=go1.22.2 go build
go: downloading go1.22.2 (darwin/amd64)
^C
$GOTOOLCHAIN=go1.23.3 go build
go: downloading go1.23.3 (darwin/amd64)
.. ...
我們看到,go完全相信我們顯式指定的工具鏈版本,即使是不滿足依賴module的最低go版本要求的!
想必大家已經(jīng)感受到支持新向前兼容規(guī)則帶來的復(fù)雜性了!這里我們還沒有顯式使用到toolchain指令行呢!但其實,在上述場景中,雖然我們沒有在go.mod中顯式使用toolchain指令行,但Go模塊會使用隱式的toolchain指令行,其隱式的默認(rèn)值為toolchain goV,其中V來自go指令行中的Go版本,比如go1.22.0等。
接下來我們就簡單地看看toolchain指令行,我們的宗旨是盡量讓事情變簡單,而不是變復(fù)雜!
3. toolchain指令行與GOTOOLCHAIN
Go mod的參考手冊[5]告訴我們:toolchain指令僅在模塊為主模塊且默認(rèn)工具鏈的版本低于建議的工具鏈版本時才有效,并建議:Go toolchain指令行中的go工具鏈版本不能低于在go指令行中聲明的所需Go版本。
也就是說如果對toolchain沒有特殊需求,我們還是盡量隱式的使用toolchain,即保持toolchain與go指令行中的go版本一致。
另外一個影響go工具鏈版本選擇的是GOTOOLCHAIN環(huán)境變量,它的值決定了go命令的行為,特別是當(dāng)go.mod文件中指定的Go版本(通過go或toolchain指令)與當(dāng)前運行的go命令的版本不同時,GOTOOLCHAIN的作用就體現(xiàn)出來了。
GOTOOLCHAIN可以設(shè)置為以下幾種形式:
- local: 這是最簡單的形式,它指示go命令始終使用其自帶的捆綁工具鏈,不允許自動下載或切換到其他工具鏈版本。即使go.mod文件要求更高的版本,也不會切換。如果版本不滿足,則會報錯。
- <name> (例如go1.21.3): 這種形式指示go命令使用特定名稱的Go工具鏈。如果系統(tǒng)中存在該名稱的可執(zhí)行文件(例如在PATH環(huán)境變量中找到了go1.21.3),則會執(zhí)行該工具鏈。否則,go命令會嘗試下載并使用名為<name>的工具鏈。如果下載失敗或找不到,則會報錯。
- auto(或local+auto): 這是默認(rèn)設(shè)置。在這種模式下,go命令的行為最為智能。它首先檢查當(dāng)前使用的工具鏈版本是否滿足go.mod文件中g(shù)o和toolchain指令的要求。如果不滿足,它會根據(jù)如下規(guī)則嘗試切換工具鏈。
- 如果go.mod中有toolchain行且指定的工具鏈名稱比當(dāng)前默認(rèn)的工具鏈更新,則切換到toolchain行指定的工具鏈。
- 如果go.mod中沒有有效的toolchain行(例如toolchain default或沒有toolchain行),但go指令行指定的版本比當(dāng)前默認(rèn)的工具鏈更新,則切換到與go指令行版本相對應(yīng)的工具鏈(例如go 1.23.1對應(yīng)go1.23.1工具鏈)。
- 在切換時,go命令會優(yōu)先在本地路徑(PATH環(huán)境變量)中尋找工具鏈的可執(zhí)行文件,如果找不到,則會下載并使用。
- <name>+auto: 這種形式與auto類似,但它指定了一個默認(rèn)的工具鏈<name>。go命令首先嘗試使用<name>工具鏈。如果該工具鏈不滿足go.mod文件中的要求,它會按照與auto模式相同的規(guī)則嘗試切換到更新的工具鏈。這種方式可以用來設(shè)定一個高于內(nèi)置版本的最低版本要求,同時又允許根據(jù)需要自動升級。
- <name>+path (或local+path): 這種形式與<name>+auto類似,也指定了一個默認(rèn)的工具鏈<name>。不同之處在于,它禁用了自動下載功能。go命令首先嘗試使用<name>工具鏈,如果不滿足要求,它會在本地路徑中搜索符合要求的工具鏈,但不會嘗試下載。如果找不到合適的工具鏈,則會報錯。
大多數(shù)情況我們會使用GOTOOLCHAIN的默認(rèn)值,即在auto模式下。但是如果在國內(nèi)自動下載go版本不便的情況下,可以使用local模式,這樣在本地工具鏈版本不滿足的情況下,可以盡快得到錯誤。或是通過<name>強(qiáng)制指定使用特定版本的工具鏈,這樣可以實現(xiàn)對組織內(nèi)采用的工具鏈版本的精準(zhǔn)控制,避免因工具鏈版本不一致而導(dǎo)致的問題。
4. 使用go get管理Go指令行和toolchain指令行
自go module誕生以來,我們始終可以使用go get對go module的依賴進(jìn)行管理,包括添加/刪除依賴,升降依賴版本等。
就像本文開頭的那個圖中所示,go命令作為全方位依賴管理器的角色定位,它不僅管理外部模塊,還負(fù)責(zé)管理Go工具鏈版本,以及越來越多的外部開發(fā)工具。因此我們也可以使用go get管理指令行和toolchain指令行。
例如,go get go@1.22.1 toolchain@1.24rc1將改變主模塊的go.mod文件,將go指令行改為go 1.22.1,將toolchain指令行改為toolchain go1.24rc1。我們要保證toolchain指令行中的版本始終等于或高于go指令行中的版本。
當(dāng)toolchain指令行與go指令行完全匹配時,可以省略和隱含,所以go get go@1.N.P時可能會刪除toolchain行。
反過來也是這樣,當(dāng)go get toolchain@1.N.P時,如果1.N.P < go指令行的版本,go指令行也會隨之被降級為1.N.P,這樣就和toolchain版本一致了,toolchain指令行可能會被刪除。
我們也可以通過下面go get命令顯式刪除toolchain指令行:
$go get toolchain@none
通過go get管理Go指令行和toolchain指令行還會對require中依賴的go module版本產(chǎn)生影響,反之使用go get管理require中依賴的go module版本時,也會對Go指令行和toolchain指令行的版本產(chǎn)生影響!不過這一切都是通過go get自動完成的!下面我們通過示例來具體說明一下。
我們首先通過示例看看go get管理go指令行對require中依賴的Go模塊版本的影響。
當(dāng)你使用go get升級或降級go.mod文件中的go指令行時,go get 會根據(jù)新的Go版本要求,自動調(diào)整require指令行中依賴模塊的版本,以滿足新的兼容性要求。比如下面這個升級go版本導(dǎo)致依賴模塊升級的示例。
假設(shè)你的模塊mymodule的go.mod文件內(nèi)容如下:
module example.com/mymodule
go 1.21.0
require (
example.com/moduleA v1.1.0 // 兼容Go 1.21.0
example.com/moduleB v1.2.0 // 兼容Go 1.21.0
)
example.com/moduleA和example.com/moduleB的v1.1.0和v1.2.0版本都只兼容到Go 1.21.0。
現(xiàn)在,你執(zhí)行以下命令升級Go版本:
$go get go@1.23.1
go get會將go.mod文件中的go指令行更新為go 1.23.1。同時,它會檢查require指令行中的依賴模塊,發(fā)現(xiàn)example.com/moduleA和example.com/moduleB的v1.1.0和v1.2.0版本可能不兼容Go1.23.1。
假設(shè)example.com/moduleA和example.com/moduleB都有更新的版本v1.3.0,且兼容Go 1.23.1,那么go get會自動將require指令行更新為:
module example.com/mymodule
go 1.23.1
require (
example.com/moduleA v1.3.0 // 兼容Go 1.23.1
example.com/moduleB v1.3.0 // 兼容Go 1.23.1
)
如果找不到兼容Go 1.23.1 的版本,go get可能會報錯,提示無法找到兼容新Go版本的依賴模塊。
同理,降低go版本也可能觸發(fā)require中依賴模塊降級。我們來看下面示例:
假設(shè)你的模塊mymodule的go.mod文件內(nèi)容如下:
module example.com/mymodule
go 1.23.1
require (
example.com/moduleA v1.3.0 // 兼容 Go 1.22.0及以上
example.com/moduleB v1.3.0 // 兼容 Go 1.22.0及以上
)
現(xiàn)在,你執(zhí)行以下命令降低go版本:
$go get go@1.22.0
執(zhí)行以上命令后,go.mod文件內(nèi)容變?yōu)椋?/p>
module example.com/mymodule
go 1.22.0
require (
example.com/moduleA v1.1.0 // 兼容Go 1.21.0及以上
example.com/moduleB v1.2.0 // 兼容Go 1.21.0及以上
)
在這個例子中, go get go@1.22.0命令會將go指令行降級為go 1.22.0, 同時, go get會自動檢查所有依賴項, 并嘗試將它們降級到與go 1.22.0兼容的最高版本。在這個例子中, example.com/moduleA和example.com/moduleB都被降級到了與go 1.22.0兼容的最高版本。
反過來,使用go get管理require中依賴的Go模塊版本時,也會對go指令行產(chǎn)生影響,我們看一個添加依賴導(dǎo)致go指令行版本升級的示例。
假設(shè)你的模塊mymodule的go.mod文件內(nèi)容如下:
module example.com/mymodule
go 1.21.0
require (
example.com/moduleA v1.1.0 // 兼容 Go 1.21.0
)
現(xiàn)在,你需要添加一個新的依賴項example.com/moduleC,而example.com/moduleC的最新版本v1.2.0的go.mod文件中指定了go 1.22.0:
// example.com/moduleC 的 go.mod
module example.com/moduleC
go 1.22.0
require (
...
)
你執(zhí)行以下命令添加依賴:
$go get example.com/moduleC@v1.2.0
go get會發(fā)現(xiàn)example.com/moduleC的版本v1.2.0需要 Go 1.22.0,而你的模塊當(dāng)前只兼容Go 1.21.0。因此,go get會自動將你的模塊的go.mod文件更新為:
module example.com/mymodule
go 1.22.0
require (
example.com/moduleA v1.1.0 // 兼容Go 1.21.0
example.com/moduleC v1.2.0 // 需要Go 1.22.0
)
go指令行被升級到了go 1.22.0,以滿足新添加的依賴項的要求。
不過無論如何雙向影響,我們只要記住一個原則就夠了,那就是go get和go mod tidy命令使go指令行中的Go版本始終保持大于或等于任何所需依賴模塊的go指令行中的Go版本。
5. 小結(jié)
本文深入探討了Go語言在版本管理和工具鏈兼容性方面的重要變革,特別是Go 1.21及以后的版本如何強(qiáng)化向前兼容性。在文章里,我強(qiáng)調(diào)了向后兼容性和向前兼容性在開發(fā)體驗中的重要性,以及如何通過go指令和新引入的toolchain指令來管理工具鏈版本。
通過文中的示例,我展示了如何在不同場景下處理Go模塊的兼容性問題,并解釋了GOTOOLCHAIN環(huán)境變量如何影響工具鏈選擇。最后,我還舉例說明了如何通過使用go get命令有效管理Go指令和依賴模塊的版本,確保代碼的可維護(hù)性和穩(wěn)定性。
不過我們也看到了,為了實現(xiàn)精確的向前兼容,Go引入了不少復(fù)雜的規(guī)則,短時間內(nèi)記住這些規(guī)則還是有門檻的,我們只能在實踐中慢慢吸收和理解。
本文涉及的源碼可以在這里[6]下載。
參考資料
- Go Toolchains[7] - https://go.dev/doc/toolchain
- Forward Compatibility and Toolchain Management in Go 1.21[8] - https://go.dev/blog/toolchain
參考資料
[1] Go1兼容性承諾: https://go.dev/doc/go1compat
[2] Go 1.21版本: https://tonybai.com/2023/08/20/some-changes-in-go-1-21
[3] 引入了tool指令: https://tonybai.com/2024/12/17/go-1-24-foresight-part2/
[4] Go 1.18版本中才引入的泛型特性: https://tonybai.com/2022/04/20/some-changes-in-go-1-18
[5] Go mod的參考手冊: https://go.dev/ref/mod#go-mod-file-toolchain
[6] 這里: https://github.com/bigwhite/experiments/tree/master/toolchain-directive
[7] Go Toolchains: https://go.dev/doc/toolchain
[8] Forward Compatibility and Toolchain Management in Go 1.21: https://go.dev/blog/toolchain