Go言語でOSSを開発しているみなさん、go.modのgo directiveにはどのような値を指定していますか?もしGo 1.25が最新のメジャーリリースである2025年12月現在で
go 1.25.0
と書いているならば、この機会にぜひ
go 1.24.0
に改めてくれないか、という話をします。同じような話はいろんなエントリや登壇で触れているのですが、この話題に主題を絞って解説します。
前提知識
Goのリリースポリシー
Go言語では半年に1回メジャーリリースがやってきます。今年(2025年)は2月11日にGo 1.24.0、8月12日にGo 1.25.0がリリースされています。
そして、security fixなどのサポート対象となるメジャーリリースは新しいもの2つ分となっています。2025年12月現在、サポートされているGoのメジャーリリースは1.24・1.25の2つです。来年2026年の2月にGo 1.26.0がリリースされたら、Go 1.24はサポートが終了するという流れです。
Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release.
https://go.dev/doc/devel/release#policy
go.modのgo directiveの意味
go.modのgo directiveにはGoのバージョンが指定されます。これは単に使用するGoのバージョンを表すのではなく、そのモジュールが要求するGoの最小バージョンを意味します。
The go directive sets the minimum version of Go required to use this module. Before Go 1.21, the directive was advisory only; now it is a mandatory requirement: Go toolchains refuse to use modules declaring newer Go versions.
https://go.dev/ref/mod#go-mod-file-go
go directiveが新しすぎると起こる問題
さて、冒頭の問題提起である、2025年12月現在において
go 1.25.0
という宣言でどう困るかという話をします。
公式でサポートされているGoのバージョンなのにビルドできない
go directiveはビルド下限を表すので、そこに最新のメジャーリリース系統のバージョンを指定していると、それ未満のバージョンではそのmoduleをビルドできなくなります。そして「それ未満のバージョン」には、Goとしてサポート対象となっている1つ前のメジャーリリースが含まれます。すなわち、まだGo公式によってサポートされているGoのツールチェーンなのにもかかわらず、そのmoduleのビルドには利用できなくなってしまいます。
Go側でサポートされているGoのメジャーリリース2つをそのままサポート対象とするOSSはそれなりに多く見られます*1。例として、opentelemetry-goの互換性に関する記述を挙げましょう:
OpenTelemetry-Go ensures compatibility with the current supported versions of the Go language
https://github.com/open-telemetry/opentelemetry-go?tab=readme-ov-file#compatibility
ここだけ見ると、「いや、このModuleは最新のバージョンしかサポートしないので」というポリシーならば許容できそうですが、実は思ったよりその選択の影響は大きいのです。
依存するGo Modulesの影響を受けてビルドできるGoの範囲が狭まる
先ほどの問題は、goディレクティブが新しすぎるModuleそのもののビルドに留まらず、そのModuleに依存するModuleにも波及します。
外部のGo Modulesに依存したライブラリModuleを作るとします。また、Go言語としてサポートされている2つのメジャーリリースはこのライブラリでもサポート対象とすることにします。
このとき、依存先のmoduleでgo 1.25.5と宣言されているならば、依存元のmoduleでもgo 1.25.5以上にしなければなりません。自分の作ったライブラリでGo 1.24をサポートしたくなっても、依存先の事情に引きずられてしまうわけです。この場合、依存先のmoduleを古いバージョンにしたり、あるいは依存先のModuleの利用を諦めたりしなくてはなりません。
この問題に関しても、module内のpackageが外部から参照されないようにinternal/配下に押し込むなどすれば許容できそうですが、さらにもう一つ落とし穴があります。
Go 1.24からgo.modにtool directiveが追加され、開発に必要なツールの依存を管理できるようになりました。tools.goを用いたワークアラウンド*2を利用せずとも、Go製のツールとそのバージョンを管理し、実行することができます。
なんと、依存先のgo directiveの影響を依存元が受けてしまう問題がtoolにおいても発生します。
たとえば、GoReleaserのgo directiveはv2.13.1現在でgo 1.25.5と宣言されています*3。最新のGoReleaserを以下のようにtoolに含めてしまうと、そのmoduleのgo directiveは1.25.5未満に設定することができなくなります。
go 1.25.5 // 1.24.0にはできない
tool https://github.com/goreleaser/goreleaser/v2
require (
github.com/goreleaser/goreleaser/v2 v2.13.1 // indirect
...
)
このように、ライブラリではなくツールの提供だけを目的としたModuleでも、go directiveが新しすぎることにより依存が困難になってしまうケースがあります。
どうなっていて欲しいか
go directiveはビルド下限を表すので、基本的には現在公式でサポートされているGoのバージョンはカバーするように宣言して欲しいです。
Go 1.25が最新のリリースである現在では、以下のように(あるいは以下よりもっと古くなるように)宣言して欲しいです:
go 1.24.0
なぜpatch versionを0にするのか
なぜ1つ前のメジャーリリースの中で最新のパッチであるgo 1.24.9ではなくgo 1.24.0を指定した方が良いのでしょうか。
この理由は、同様の内容がgolang.org/x配下のmoduleのgo directiveを自動更新するproposalの中に書かれていて共感したので引用します:
Another option would be to always use the latest 1.(N-1).X, updating all the x repos each time a new minor Go release comes out. That forces everyone to update to that new minor release in order to incorporate any new x repo changes, which seems too aggressive. As much as we try to avoid it, minor Go releases do sometimes contain bugs, and it should be possible to choose to use older ones if needed.
https://go.googlesource.com/proposal/+/HEAD/design/69095-x-repo-continuous-go.md#why-not-bump-on-each-minor-release
要約すると、新しいパッチバージョンへのアップデートを強制するのは強引なので選択の余地を設けたということです。ある時点で最新のパッチバージョンにバグが含まれていないとも限りませんから。
go directiveを古くすることによるデメリットと対応
ここからは、go.modのgo directiveに古いメジャーリリースを指定しておくことにより発生するデメリットと、それに対してどうハンドリングしたらいいのかについて説明します。
すぐに最新の言語機能が使えない
Go言語のメジャーリリースには毎回嬉しいアップデート入っているように私は思います。しかし、go directiveを1つ前のメジャーリリースに保つということは、最新の言語機能を利用することはまだできないということになります。
この問題に対して、私は残念ながら諦めています。Goのメジャーリリースが出ても、この機能はあと半年後に使えるようになるな〜ぐらいのつもりで向き合っています。
もちろん、メジャーリリースが出たタイミングで2つ前のメジャーリリースがサポート対象から落ちるため、このタイミングで1つ前のメジャーリリースの言語機能が新たに利用可能になります。リリースにワイワイするタイミングは同じで、その対象が1つ遅れているだけです。
GitHub ActionsでのGoバージョン指定に困る(困らない)
actions/setup-goのgo-version-file inputにgo.modファイルを指定することでCI環境のGoのバージョンを決めているケースがあるでしょう。
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
setup-goはgo.modのgo directiveを読んで、その値と一致するGoのバージョンを利用する*4ため、go directiveが最新でないと古いツールチェーンがビルドやテストで利用されてしまいます。
これに対する対応としては、go.modのtoolchain directiveを利用するのが良いと思います。toolchain directiveは「そのモジュールで推奨されるツールチェーンとそのバージョン」を宣言するものです。
A toolchain directive declares a suggested Go toolchain to use with a module.
https://go.dev/ref/mod#go-mod-file-toolchain
setup-goのv6から、go.modのtoolchain directiveも読んでバージョンを決定するように、かつこの挙動がgo directiveの参照よりも優先されるようになりました。
github.com
よって、以下のようなgo.modを用意することで、go directiveを1つ前のメジャーリリースバージョンに保ちつつ、GitHub Actionsで使われるGoのバージョンを別に指定することが可能です:
go 1.24.0
toolchain go1.25.5
toolchain directiveはRenovateの標準設定で継続的に更新することができるため、最新に保つ労力が少ないのも魅力です。
In go.mod, the toolchain directive essentially means "Use this exact version of go". Unlike the go directive, it's valid to keep bumping this, and you should see updates to it proposed by default.
https://docs.renovatebot.com/modules/manager/gomod/#updating-of-go-mod-and-toolchain-directives *5
Go 1.26からgo mod initで作られるgo.modのgo directiveの値が変わりそう
さて、いよいよ2026年2月にGo 1.26のリリースが控えています。現時点でもすでにドラフトのリリースノートを閲覧することができます。
このリリース予定の内容のひとつに、go mod initコマンドで作られるgo.modファイルのgo directiveの値が1つ古いメジャーリリースのバージョンになるというものがあります。
go mod init now defaults to a lower go version in new go.mod files. go mod init using a toolchain of version 1.N.X will create a go.mod file specifying the Go version go 1.(N-1).0.
https://go.dev/doc/go1.26#go-command
これまでgo mod initを実行した時には、使用しているツールチェーンのバージョンがそのままgo directiveにセットされていました。
$ go version
go version go1.25.5 darwin/arm64
$ go mod init hoge
go: creating new go.mod: module hoge
$ cat go.mod
module hoge
go 1.25.5
これが、以下の挙動になるイメージです(実際にGo 1.25.5でこのようになるわけではなく、あくまでイメージです):
module hoge
-go 1.25.5
+go 1.24.0
この変更理由について、リリースノートには「現在サポートされているGoのバージョンと互換性のあるモジュールの作成を奨励するため」と書かれています。
This is intended to encourage the creation of modules that are compatible with currently supported versions of Go.
https://go.dev/doc/go1.26#go-command
Goチームによるこの変更意図を読んでも、やはりgo directiveを最新ではなく1つ前のメジャーリリースに保つことに相当の妥当性があると考えられるでしょう。
まとめ
go.modはgo directiveはGo Moduleのビルド下限を表すものです。また、go directiveにより宣言された下限バージョンは依存元に伝播していきます。go directiveを最新のバージョンで指定してしまうと、Goがサポートしているバージョンを利用しているにも関わらずビルドできないModuleになってしまいます。そしてそれが伝播してどんどん世の中に増えてしまいます。
外部から依存され得るGoのOSSプロジェクトでは、go directiveを最大でも1つ前のメジャーリリースにしてみませんか?バイナリが成果物の場合にはtoolchain directiveを利用して推奨ツールチェーンを宣言し、ビルド下限バージョンと利用するバージョンを分離して宣言してみませんか?というお話でした。