この記事はZOZO AdventCalender 2024シリーズ5の7日目の記事です。
はじめに
今年の2月にリリースされたGo 1.22でGo言語で長らく問題とされていたクロージャキャプチャ問題が修正されました。
Go 1.22のリリースからだいぶ時間も経っていて、Go 1.23もリリースされた今、旬を過ぎた話題ではありますが1.21以下のバージョンで実装していて、ちょうどこの問題を踏んだので、改めてGo言語のクロージャキャプチャ問題についてまとめてみます。
また去年のAdventCalenderでは複数バージョンのGo言語をインストールする方法について書きましたが、Go1.21から導入されているGOTOOLCHAINを使うことで、複数バージョンのGo言語をインストールする必要がなくなりました。
そのことについても簡単に触れていきます。
Go言語のクロージャキャプチャ問題
Go言語のクロージャキャプチャ問題は、FAQにも掲載されるぐらい有名な問題です。
FAQにも掲載されている通り、1.22で修正されているので最新バージョンのGo言語を使用する分には意識しなくても問題ありません。
Go言語のクロージャ
Go言語におけるクロージャは、関数リテラルを使って実装します。
参考: 公式ドキュメント
func main() {
x := 10
f := func() {
fmt.Println(x)
}
f()
}
改めてクロージャキャプチャ問題とは
クロージャキャプチャ問題は、クロージャが外部変数をキャプチャする際に、その変数がループ変数の場合に発生します。
以下の例を見てみましょう。
func main() {
var fs []func()
for i := 0; i < 3; i++ {
fs = append(fs, func() {
fmt.Println(i)
})
}
for _, f := range fs {
f()
}
}
Goのバージョン1.21.x以下でこのコードを実行すると、以下のような出力が得られます。
$ GOTOOLCHAIN=1.21.9 go run main.go
3
3
3
$
この出力が得られる理由は、クロージャが変数i
をキャプチャする際に、i
がループ変数であるためです。
クロージャは、外部変数を参照する際に、その変数の値をコピーするのではなく、参照するため、i
の値が変わると、クロージャから参照されるi
の値も変わります。
そのため、i
の値が最終的に3
になると、クロージャから参照されるi
の値も3
になります。
これが、Go言語のクロージャキャプチャ問題です。
1.22での修正
1.22ではforループの実装が修正され、ループ変数が繰り返しごとのスコープを持つようになりました。
参考:
そのため、上記のコードを1.22以降で実行すると、以下のような出力が得られ問題が解消されていることが確認できます。
$ GOTOOLCHAIN=1.22.0 go run main.go
0
1
2
$
GOTOOLCHAIN
クロージャキャプチャ問題の実行例でGOTOOLCHAIN
という環境変数を使っています。
Go 1.21から導入されたGOTOOLCHAIN
は、Go言語のツールチェーンを管理するための環境変数です。
GOTOOLCHAIN
を使うことで、複数バージョンのGo言語を事前にインストールする必要がなくなりました。
公式ドキュメント
GOTOOLCHAINの使い方
GOTOOLCHAIN は以下の形式で設定します
GOTOOLCHAIN=[<バージョン>|<バージョン>+auto|<バージョン>+path:<パス>]
デフォルト値はauto
です。
またauto
は、local+auto
の短縮形です。同様にpath
はlocal+path
の短縮形です。
GOTOOLCHAIN
にlocal
と指定すると、常にローカル環境にインストールされているGoを使用します。
そして+auto
(または短縮形のauto
)が設定されている場合、必要に応じて新しいバージョンのGoを選択して実行します。
具体的には、ワークスペースのgo.workファイルや、go.modファイルに記載されているGoのバージョンを使用します。
GOTOOLCHAIN
導入前まではあまり意味を持たなかったgo.modファイルのgo
ディレクティブが、GOTOOLCHAIN
導入により意味を持つようになり、プロジェクトのGoバージョンを指定する際にも使えるようになったのも便利な点です。
GOTOOLCHAIN=1.22
と指定すると、Go 1.22を使用します。
実行しようとしているGoのバージョンが、まだダウンロードされていない場合、自動的にダウンロードされてキャッシュされます。
設定方法
GOTOOLCHAINの設定は以下の優先順で行われます。
- 環境変数
GOTOOLCHAIN
-
go env
による設定 -
$GOROOT/go.env
による設定
$GOROOT/go.env
ファイルが存在しないか、他の方法で設定されていない場合はGoコマンドはGOTOOLCHAIN=local
が設定されているものとして動作します。
まとめ
- Go言語のクロージャキャプチャ問題についてまとめました。
- Go 1.22以降であれば問題ないですが、1.21以下のバージョンで実装している場合は注意が必要です。
- また、Go 1.21から導入されたGOTOOLCHAINを使うことで、複数バージョンのGo言語をインストールする必要がなくなりました。以前のバージョンでの動作を確認するのも簡単になりました。