4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZOZOAdvent Calendar 2024

Day 7

[Go]今さらクロージャキャプチャ問題 & GOTOOLCHAINについて少しだけ

Last updated at Posted at 2024-12-06

この記事は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()
}

改めてクロージャキャプチャ問題とは

クロージャキャプチャ問題は、クロージャが外部変数をキャプチャする際に、その変数がループ変数の場合に発生します。
以下の例を見てみましょう。

main.go
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の短縮形です。同様にpathlocal+pathの短縮形です。
GOTOOLCHAINlocalと指定すると、常にローカル環境にインストールされているGoを使用します。
そして+auto(または短縮形のauto)が設定されている場合、必要に応じて新しいバージョンのGoを選択して実行します。
具体的には、ワークスペースのgo.workファイルや、go.modファイルに記載されているGoのバージョンを使用します。
GOTOOLCHAIN導入前まではあまり意味を持たなかったgo.modファイルのgoディレクティブが、GOTOOLCHAIN導入により意味を持つようになり、プロジェクトのGoバージョンを指定する際にも使えるようになったのも便利な点です。
GOTOOLCHAIN=1.22と指定すると、Go 1.22を使用します。
実行しようとしているGoのバージョンが、まだダウンロードされていない場合、自動的にダウンロードされてキャッシュされます。

設定方法

GOTOOLCHAINの設定は以下の優先順で行われます。

  1. 環境変数GOTOOLCHAIN
  2. go envによる設定
  3. $GOROOT/go.envによる設定

$GOROOT/go.envファイルが存在しないか、他の方法で設定されていない場合はGoコマンドはGOTOOLCHAIN=localが設定されているものとして動作します。

まとめ

  • Go言語のクロージャキャプチャ問題についてまとめました。
  • Go 1.22以降であれば問題ないですが、1.21以下のバージョンで実装している場合は注意が必要です。
  • また、Go 1.21から導入されたGOTOOLCHAINを使うことで、複数バージョンのGo言語をインストールする必要がなくなりました。以前のバージョンでの動作を確認するのも簡単になりました。
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?