Pętla for ... range pozwala na wygodne przechodzenie przez elementy slice, mapy, tablicy lub łańcucha.
Przykład:
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) }
Kluczowa subtelność:
Zmienne i, v, k itp. są używane ponownie w każdej iteracji! Często prowadzi to do błędów przy przekazywaniu ich przez odwołanie wewnątrz pętli lub uruchamianiu goroutine wewnątrz range.
Co się stanie, jeśli wewnątrz range dla slice uruchomimy goroutine, przechwycając zmienną iteracyjną? Jak uniknąć błędu?
Odpowiedź: Pojawia się typowy błąd: wewnątrz goroutine używane są zmienne pętli, które po zakończeniu pętli będą miały ostatnią wartość. Aby tego uniknąć — należy tworzyć lokalne kopie:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Historia
W jednym projekcie używano range do wypełnienia slice przez kilka goroutine, zapominając o tworzeniu kopii zmiennych pętli. Wszystkie goroutine wydrukowały tę samą wartość — ostatnią z tablicy, co poważnie zaszkodziło logice biznesowej.
Historia
Podczas używania range dla mapy, odwołanie do wartości zapisywano w nowym slice wskaźników. W wyniku tego wszystkie elementy nowego slice odnosiły się do tej samej zmiennej — tej, która była używana w pętli (kopię wartości). Błąd ujawnił się przy aktualizacji tych zmiennych poza pętlą (panic: invalid memory address lub nieoczekiwane dane).
Historia
W wewnętrznym narzędziu przy użyciu range dla string uruchamiałem procesory ciężkich podciągów, ale dla każdej iteracji otrzymywałem przesunięcie w bajtach, a nie w znakach Unicode. Skutek: nieprawidłowe przetwarzanie dla ciągów Unicode, nieprawidłowe cięcie znaków.