for ... range ループは、スライス (slice)、マップ (map)、配列、または文字列の要素を便利に繰り返し処理することができます。
例:
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
重要な注意点:
変数 i、v、k などはすべてのイテレーションで再利用されます!これは、ループ内で参照渡しを行ったり、range 内で goroutine を起動したりするときにバグの原因となることがよくあります。
スライス内で goroutine を起動し、イテレーション変数をキャプチャした場合、どうなりますか?どのようにエラーを回避することができますか?
回答: 典型的なエラーが発生します:goroutine 内で使用されるループ変数は、ループの終了後に最後の値を持ちます。回避するには、ローカルコピーを作成する必要があります:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
事例
あるプロジェクトでは、複数の goroutine を介してスライスを填補するために range を使用しましたが、ループ変数のコピーを作成するのを忘れてしまいました。すべての goroutine が配列の最後の値を印刷し、ビジネスロジックが大きく損なわれました。
事例
マップの range では、値への参照を新しいポインタのスライスに保存しました。その結果、新しいスライスのすべての要素が、ループ内で使用される同じ変数(値のコピー)を参照しました。この変数をループの外で更新した時にバグが発生しました(panic: invalid memory address または予期しないデータ)。
事例
内部ツールで、string の range 中に重要な部分文字列のハンドラを起動しましたが、各イテレーションでバイトのオフセットを取得し、Unicode の文字ではありませんでした。結果として、Unicode 文字列の不正な処理や文字の不正なスライスが発生しました。