😎

【Go】Toolchainで指定したGoバージョンの機能が使われない

2025/03/22に公開

問題

普段検証に使用しているリポジトリでGo1.22以前と1.23以降のtime.Timerの挙動をそれぞれ調べていた。
しかし、1.23以降で動かすためにGOTOOLCHANI=go1.23.0を指定しているのだがどうにも挙動が変わっていない。
にも関わらず、runtime.Version()はgo1.23.0を返してくる。

// GOTOOLCHAIN=go1.23.0 go run . で実行

func main() {
	fmt.Println("test start")
	fmt.Println(runtime.Version()) // go1.23.0

	// time.Timerの挙動確認
	// 1.22以前と変わっていないように見える

	fmt.Printf("test end")
}

結論

モジュールのgo.modのgoディレクティブが1.22.2になっていた。

go.mod
module playgo

go 1.22.2 <- here

require (
  ...
)

以前Toolchainは別ブログでまとめていたのにも関わらず、まんまと自分がハマってしまった。

Toolchainを用いても用いなくてもbuild時のセマンティクスの検証はgo.modのgoディレクティブのバージョンで行われる

つまり今回の例で言うと、GOTOOLCHAIN=go1.23.0 go run .を実行したとき、実行バージョンはgo1.23.0だが、build時にはgoディレクティブの1.22.2のセマンティクスで解釈されていたということだ。

普通、goディレクティブが1.22.2であるならば、ソースコードに1.22.3以降の記法があるとエディタのリントで弾かれるし、ビルドしたとしても失敗するのであまり問題はない。

今回このような問題が起きてしまったのは、time.Timerの公開APIはバージョン間で変わっておらず、内部的な挙動のみ変化していたからだ。

というわけで、go.modのgoディレクティブを1.23.0を再度実行してみたところ、期待した通りの挙動の変化が見られた。

ちなみに、これに気づいたのは公式ドキュメントの下記一文を読んだから。

The new implementation is only used in programs where package main is in a module with a go.mod declaring go 1.23 or later. Other programs continue to use the old semantics. The GODEBUG setting asynctimerchan=1 forces the old semantics; conversely, asynctimerchan=0 forces the new semantics.
新しい実装は、go.modでgo 1.23以降を宣言したモジュール内にmainパッケージがあるプログラムでのみ使用される。その他のプログラムでは、引き続き古いセマンティクスが使われます。GODEBUG設定のasynctimerchan=1は古いセマンティクスを強制し、逆にasynctimerchan=0は新しいセマンティクスを強制します。

最後に

同じようなことで悩む人がいるかはわからないが、誰かの参考になれば嬉しい。

もしこの話で登場したToolchainに馴染みが無ければ、拙著苦しんで覚えたGo Toolchainの挙動を解説するを参考にして欲しい。

Discussion