Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fixing For Loops in Go 1.22

uncle
January 17, 2024

Fixing For Loops in Go 1.22

uncle

January 17, 2024
Tweet

More Decks by uncle

Other Decks in Technology

Transcript

  1. The Problem このコードを見てみましょう func main() { done := make(chan bool)

    values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  2. The Problem このコードはどのように Printされると思いますか? func main() { done := make(chan

    bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  3. The Problem 実は... func main() { done := make(chan bool)

    values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  4. The Problem 実は... "c", "c", "c" とPrintされます https://go.dev/play/p/mjXjtKeT6JD func main()

    { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  5. The Problem ループの各繰り返しで変数vの 同じインスタンスが使用される つまり変数のスコープがループ全体 になってる 各クロージャは単一の変数を共有 func main() {

    done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  6. The Problem クロージャが実行されると fmt.Printlnが実行された時点での 変数vの値を出力 func main() { done :=

    make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  7. The Problem 変数vの値が無名関数に 引数として渡される その値はその後 関数内で変数uとしてアクセス可能 https://go.dev/play/p/xeWv2pldX6c func main() {

    done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { go func(u string) { fmt.Println(u) done <- true }(v) } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  8. The Problem もっと簡単な方法は 新しい変数を作成すること これで想定通り動く https://go.dev/play/p/xeWv2pldX6c func main() { done

    := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { v := v // create a new 'v'. go func() { fmt.Println(v) done <- true }() } // wait for all goroutines to complete before exiting for _ = range values { <-done } }
  9. The Problem この例だと "4", "4", "4" が出力される https://go.dev/play/p/lM8kkEPu8Tw func main()

    { var prints []func() for i := 1; i <= 3; i++ { prints = append(prints, func() { fmt.Println(i) }) } for _, print := range prints { print() } }
  10. The Impact このような偶数であることを確認する テストがあったとする func TestAllEvenBuggy(t *testing.T) { testCases :=

    []int{1, 2, 4, 6} for _, v := range testCases { t.Run("sub", func(t *testing.T) { t.Parallel() if v&1 != 0 { t.Fatal("odd v", v) } }) } }
  11. The Impact Go 1.21では、このテストはパスする t.Parallelは各サブテストをブロックし ループ全体が完了するまで待機 その後すべてのサブテストを 並行して実行するから func TestAllEvenBuggy(t

    *testing.T) { testCases := []int{1, 2, 4, 6} for _, v := range testCases { t.Run("sub", func(t *testing.T) { t.Parallel() if v&1 != 0 { t.Fatal("odd v", v) } }) } }