Achtergrond van de vraag:
De for-range constructie is geïntroduceerd in Go als een manier om door collecties (slices, arrays, maps, strings) te itereren. De Go-ontwikkelaars hebben een optimalisatie geïmplementeerd: bij elke iteratie van de lus vindt er een waarde kopie plaats, in plaats van dat er direct via referentie wordt gewerkt, wat kan leiden tot onduidelijke fouten, vooral met lusvariabelen.
Probleem:
Veel mensen maken fouten wanneer ze proberen het adres van een variabele binnen range te nemen (bijvoorbeeld &v), waarbij ze denken dat ze het adres van het element in de collectie krijgen, terwijl ze eigenlijk het adres van een lokale variabele krijgen.
Oplossing:
In de for-range lus worden bij elke iteratie nieuwe kopieën van de iteratorvariabelen (key, value) aangemaakt. Voor eenvoudige types is dit onschadelijk, maar voor structuren leidt dit tot onverwachte problemen bij het bewaren van een pointer naar een element — deze zal altijd naar dezelfde variabele wijzen, niet naar verschillende elementen van de slice.
Codevoorbeeld:
people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // alle ptrs verwijzen naar dezelfde p }
Belangrijke kenmerken:
Wat gebeurt er als je referenties naar de value-variabele binnen range opslaat?
Alle referenties zullen naar hetzelfde geheugen wijzen, aangezien value een tijdelijke variabele is.
for _, v := range someSlice { ptrs = append(ptrs, &v) } // Alle ptrs bevatten een verwijzing naar dezelfde variabele!
Kun je een element van de collectie via referentie veranderen via value in range?
Nee, het veranderen van value heeft geen invloed op het originele element van de collectie. Voor wijzigingen moet je naar de index verwijzen.
for _, v := range arr { v.Field = 10 // arr verandert niet } for i := range arr { arr[i].Field = 10 // correct }
Garandeert for-range over map een consequente volgorde van iteratie?
Nee, de volgorde van iteratie over maps in Go is niet gedefinieerd en kan verschillen bij elke uitvoering van de applicatie.
Een ontwikkelaar probeert een lijst van referenties naar elementen van een structuur te serialiseren via range en slaat &value op in een aparte slice. Het resultaat is een slice van identieke adressen.
Voordelen:
Nadelen:
Ze itereren op index en bewaren de pointer naar het gewenste element van de array:
for i := range arr { ptrs = append(ptrs, &arr[i]) }
Voordelen:
Nadelen: