Historia pytania:
Konstrukcja for-range pojawiła się w Go jako sposób iteracji po kolekcjach (wycinkach, tablicach, mapach, stringach). Twórcy Go wdrożyli optymalizację: w każdej iteracji pętli zachodzi kopiowanie wartości, a nie bezpośrednie użycie przez referencję, co może prowadzić do nieoczywistych błędów, zwłaszcza z zmiennymi pętli.
Problem:
Wiele osób popełnia błąd próbując wziąć adres zmiennej wewnątrz range (np. &v), myśląc, że otrzymują adres elementu kolekcji, podczas gdy w rzeczywistości otrzymują adres lokalnej zmiennej.
Rozwiązanie:
W pętli for-range w każdej iteracji tworzone są nowe kopie zmiennych iteratora (key, value). Dla prostych typów jest to bezbolesne, ale dla struktur prowadzi do nieoczekiwanych efektów przy zapisywaniu wskaźnika na element – zawsze będzie wskazywał na tę samą zmienną, a nie na różne elementy wycinka.
Przykład kodu:
people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // wszystkie ptrs będą wskazywały na ten sam p }
Kluczowe cechy:
Co się stanie, gdy zapiszemy wskaźniki na zmienną value wewnątrz range?
Wszystkie wskaźniki będą wskazywały na tę samą pamięć, ponieważ value jest zmienną tymczasową.
for _, v := range someSlice { ptrs = append(ptrs, &v) } // Wszystkie ptrs zawierają wskaźnik na tę samą zmienną!
Czy można zmienić element kolekcji przez referencję przez value w range?
Nie, zmiana value nie dotyka oryginalnego elementu kolekcji. Aby dokonać zmiany, należy odwołać się przez indeks.
for _, v := range arr { v.Field = 10 // arr się nie zmieni } for i := range arr { arr[i].Field = 10 // poprawnie }
Czy for-range dla map gwarantuje sekwencyjny porządek przechodzenia?
Nie, porządek iteracji po mapach w Go nie jest określony i może być inny przy każdym uruchomieniu aplikacji.
Programista próbuje serializować listę wskaźników na elementy struktury przez range i zapisuje &value w osobnym wycinku. Powstaje wycinek o identycznych adresach.
Plusy:
Minusy:
Iterują przez indeks i zapisują wskaźnik na odpowiedni element tablicy:
for i := range arr { ptrs = append(ptrs, &arr[i]) }
Plusy:
Minusy: