【Go】Toolchainで指定したGoバージョンの機能が使われない
問題
普段検証に使用しているリポジトリで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になっていた。
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