Goブログ

Go 1.21における前方互換性とツールチェーン管理

Russ Cox
2023年8月14日

Go 1.21は、強化された後方互換性への取り組みに加えて、Goコードのより優れた前方互換性も導入しています。つまり、Go 1.21以降では、さらに新しいバージョンのGoを必要とするコードを誤ってコンパイルしないよう、より適切な配慮がなされます。具体的には、go.modgo行で、必要なGoツールチェーンの最小バージョンが指定されるようになりました。以前のリリースでは、これはほとんど強制されない提案でした。

これらの要件を容易に満たせるようにするため、Go 1.21ではツールチェーン管理も導入されました。これにより、異なるモジュールが、必要なモジュールの異なるバージョンを使用できるのと同様に、異なるGoツールチェーンを使用できるようになります。Go 1.21をインストールした後、Goツールチェーンを手動でダウンロードしてインストールする必要はもうありません。goコマンドがそれを実行します。

この投稿の残りの部分では、これらのGo 1.21の変更点の両方を詳しく説明します。

前方互換性

前方互換性とは、Goツールチェーンが、より新しいバージョンのGoを対象としたGoコードをビルドしようとしたときに何が起こるかを指します。私のプログラムがモジュールMに依存していて、M v1.2.3に追加されたバグ修正が必要な場合、require M v1.2.3を私のgo.modに追加することで、私のプログラムがMの古いバージョンに対してコンパイルされないことが保証されます。しかし、私のプログラムが特定のバージョンのGoを必要とする場合、それを表現する方法はありませんでした。特に、go.modgo行では、それが表現されていませんでした。

たとえば、Go 1.18に追加された新しいジェネリクスを使用するコードを書いた場合、go 1.18go.modファイルに書くことができますが、それによって以前のバージョンの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.modgo行をガイドラインではなくルールとして扱い、その行には特定のポイントリリースまたはリリース候補をリストできます。つまり、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 1.21.1と記載されているgo.modを持つモジュールで、たとえばgo buildというgoコマンドを実行すると、Go 1.21.0 goコマンドはGo 1.21.1が必要であることに気づき、それをダウンロードして、そのバージョンのgoコマンドを再呼び出してビルドを完了します。goコマンドがこれらの他のツールチェーンをダウンロードして実行する場合、それらをPATHにインストールしたり、現在のインストールを上書きしたりしません。代わりに、それらをGoモジュールとしてダウンロードし、モジュールのセキュリティとプライバシーの利点をすべて継承し、モジュールキャッシュから実行します。

また、特定のモジュールで作業する際に使用するGoツールチェーンの最小バージョンを指定するgo.modに新しい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のリリース候補の1つを使用している場合、特定のモジュールで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ツールチェーン」を参照してください。

次の記事: slogを使用した構造化ロギング
前の記事: 後方互換性、Go 1.21、およびGo 2
ブログインデックス