De for ... range loop maakt het eenvoudig om door de elementen van een slice, map, array of string te itereren.
Voorbeeld:
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) }
Belangrijke nuance:
De variabelen i, v, k enz. worden hergebruikt in alle iteraties! Dit wordt vaak een bron van bugs wanneer ze als referentie binnen de loop worden doorgegeven of wanneer goroutines binnen de range worden gestart.
Wat gebeurt er als je binnen een range over een slice een goroutine start, waarbij je de iteratievariabele vastlegt? Hoe kun je de fout vermijden?
Antwoord: Er ontstaat een typische fout: binnen de goroutine worden de loopvariabelen gebruikt, die na het beëindigen van de loop de laatste waarde zullen hebben. Om dit te vermijden, moet je lokale kopieën maken:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Verhaal
In een project werd range gebruikt om een slice in te vullen via meerdere goroutines, waarbij de loopvariabelen niet gekopieerd werden. Alle goroutines drukten dezelfde waarde af — de laatste uit de array, wat de bedrijfslogica ernstig verstoorde.
Verhaal
Bij range over een map werd de link naar de waarde opgeslagen in een nieuwe slice van pointers. Hierdoor verwezen alle elementen van de nieuwe slice naar dezelfde variabele — die gebruikt werd in de loop (kopie van de waarde). De bug manifesteerde zich bij het bijwerken van deze variabelen buiten de loop (panic: ongeldige geheugenadres of onverwachte gegevens).
Verhaal
In een interne tool startte ik handlers met een zware substring binnen range over een string, maar voor elke iteratie kreeg ik een offset in bytes in plaats van in Unicode-tekens. Resultaat: onjuiste verwerking voor Unicode-strings, onjuiste splitsing van tekens.