In Go, le funzioni anonime (func literals) sono in grado di creare chiusure, ovvero accedere alle variabili dall'ambiente circostante anche dopo la sua conclusione. Tali chiusure allocano memoria nel heap, se necessario per il corretto funzionamento (rilevato tramite l'analisi di escape).
Esempio:
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d\n", a(5)) // 5 printf("%d\n", a(10)) // 15
Caratteristiche:
Cosa stamperà questo codice?
func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }
Molti risponderanno che stamperà 0, 1, 2, tuttavia il risultato sarà:
3
3
3
Tutte le chiusure fanno riferimento alla stessa variabile i; dopo la conclusione del ciclo, il suo valore è 3.
Corretto: catturare una copia della variabile nel corpo del ciclo:
for i := 0; i < 3; i++ { v := i // nuova variabile fs = append(fs, func() { fmt.Println(v) }) }
Storia
Nel progetto di routing dinamico, è stato usato un ciclo per creare molti handler tramite closure, ognuno doveva catturare il proprio percorso. Di conseguenza, tutti gli handler stampavano l'ultimo percorso — non è stata creata una variabile separata in ogni chiusura. L'errore è stato scoperto solo durante l'integrazione con l'API HTTP.
Storia
Testando l'accesso concorrente tramite goroutine all'interno di un ciclo, la closure catturava il riferimento all'indice, non una copia. Questo creava effetti "casuali": i dati venivano scritti non nel proprio slot dell'array, ma nell'ultimo.
Storia
Nella funzione di raccolta statistiche, la closure modificava una variabile globale dall'ambiente esterno, anche se l'autore si aspettava un contatore indipendente per ciascun task. Il problema è stato notato a causa di una somma ricostruita in modo inadeguato, che era sempre globale, non privata, nonostante la logica locale.