Il ciclo for ... range consente di attraversare facilmente gli elementi di uno slice, di una mappa, di un array o di una stringa.
Esempio:
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) }
Sottigliezza chiave:
Le variabili i, v, k, ecc. vengono riutilizzate in tutte le iterazioni! Questo spesso diventa fonte di bug quando vengono passate per riferimento all'interno del ciclo o quando viene avviata una goroutine all'interno del range.
Cosa succede se all'interno del range su uno slice avviate una goroutine catturando la variabile di iterazione? Come evitare l'errore?
Risposta: Si verifica un errore tipico: all'interno della goroutine vengono utilizzate le variabili del ciclo, che dopo il termine del ciclo avranno l'ultimo valore. Per evitare ciò, è necessario creare copie locali:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Storia
In un progetto si utilizzava range per popolare uno slice tramite diverse goroutine, dimenticando di fare copie delle variabili di ciclo. Tutte le goroutine hanno stampato lo stesso valore — l'ultimo dell'array, il che ha gravemente compromesso la logica di business.
Storia
Durante il range su una mappa, il riferimento al valore veniva salvato in un nuovo slice di puntatori. Di conseguenza, tutti gli elementi del nuovo slice facevano riferimento alla stessa variabile — quella utilizzata nel ciclo (copia del valore). Il bug si manifestava quando venivano aggiornate queste variabili al di fuori del ciclo (panic: indirizzo di memoria non valido o dati inaspettati).
Storia
In uno strumento interno, durante il range su stringhe avviavo gestori per sottostringhe significative, ma per ogni iterazione ottenevo un offset in byte, non in caratteri Unicode. Risultato: gestione errata delle stringhe Unicode, taglio errato dei caratteri.