The Go Blog
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
$
この2つのコンパイラエラーは誤解を招くノイズです。実際の問題は、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 buildなどのgoコマンドを実行した場合、Go 1.21.0のgoコマンドはGo 1.21.1が必要であることに気づき、それをダウンロードし、そのバージョンのgoコマンドを再呼び出ししてビルドを完了します。goコマンドがこれらの他のツールチェーンをダウンロードして実行しても、それらをPATHにインストールしたり、現在のインストールを上書きしたりすることはありません。代わりに、Goモジュールとしてダウンロードし、モジュールのセキュリティとプライバシーの利点をすべて継承し、その後モジュールキャッシュからそれらを実行します。
go.modには、特定のモジュールで作業する際に使用する最小のGoツールチェーンを指定する新しいtoolchain行も追加されました。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およびtoolchainの要件は、通常のモジュールの要件と同様にgo getを使用して更新できます。たとえば、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 Toolchains」を参照してください。
次の記事:slogを使った構造化ログ
前の記事:後方互換性、Go 1.21、そしてGo 2
ブログインデックス