In Go kan de for loop constructie een initialisatiemblok (init), een voorwaarde en een postfixuitdrukking omvatten. Historisch gezien is dit mechanisme ontworpen voor het gemak van het schrijven van code en de gewoonte van C-achtige talen. Echter, in Go heeft de scope van de loopvariabele (i) unieke eigenschappen die sterk invloed hebben op het gedrag binnen geneste functies, closures en goroutines.
Probleem — bij het starten van goroutines of closures in elke iteratie van de loop kan er vaak onvoorspelbaar gedrag optreden: de variabele i wordt niet gekopieerd, maar "vastgelegd" per referentie, dat wil zeggen dat de closure verwijst naar de gedeelde variabele van de loop, die na het einde van de loop de laatste waarde aanneemt. Dit resulteert in dezelfde uitkomst in alle goroutines/closures, terwijl de logica iets anders had kunnen impliceren.
Oplossing — als het nodig is om de waarde van de variabele van elke iteratie door te geven, gebruik dan expliciete kopie van de variabele (via een extra variabele) of geef deze door als argument in de closure.
Codevoorbeeld:
for i := 0; i < 3; i++ { go func(j int) { fmt.Println(j) }(i) // Correct! Gekopieerde waarde } for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() // Fout: alle goroutines printen 3 }
Belangrijke kenmerken:
Verandert de scope van de for-variabele bij het gebruik van break of continue?
Nee. De scope van de variabele die in de for is gedeclareerd, is altijd beperkt tot het blok van die loop. Break of continue onderbreken alleen de huidige iteratie, maar "stoten" de variabele niet naar buiten.
Kan een variabele die in de init-gedeelte van for is gedeclareerd, worden vastgelegd binnen een methode buiten de loop?
Nee. De variabele is alleen zichtbaar binnen de for en alle geneste blokken, maar niet daarbuiten na het beëindigen van de loop.
Wat gebeurt er als de variabele wordt vastgelegd in een defer-uitdrukking binnen for?
Zelfde situatie: de defer-functie "ziet" niet de waarde op het moment van creatie, maar de huidige waarde van de variabele op het moment van uitvoering van defer (meestal de laatste waarde van de loop).
for i := 0; i < 3; i++ { defer fmt.Println(i) // alle defer zullen 3 printen }
In een webserver in Go heeft de ontwikkelaar meerdere goroutines gestart voor het verwerken van verschillende poorten, waarbij de poortindex als loopvariabele werd gebruikt en deze direct werd vastgelegd in de lambda-expressie. Alle goroutines verwijzen naar één poort — de laatste in de array.
Voordelen:
Nadelen:
In het team werd een regel ingevoerd — altijd de waarde van de loopvariabele naar een nieuwe variabele kopiëren, die al door de closure/goroutine wordt vastgelegd.
Voordelen:
Nadelen: