W Go funkcje anonimowe (literały funkcji) mogą tworzyć zamknięcia, co oznacza, że mają dostęp do zmiennych z otaczającego kontekstu, nawet po jego zakończeniu. Takie zamknięcia alokują pamięć na heapie, jeśli jest to potrzebne do prawidłowego działania (detekcja odbywa się za pomocą analizy ucieczki).
Przykład:
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
Cechy szczególne:
Co wyświetli ten kod?
func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }
Wielu odpowiedziałoby, że wyświetli 0, 1, 2, jednak wynik będzie:
3
3
3
Wszystkie zamknięcia odnoszą się do tej samej zmiennej i; po zakończeniu pętli jej wartość wynosi 3.
Prawidłowo: przechwycić kopię zmiennej w ciele pętli:
for i := 0; i < 3; i++ { v := i // nowa zmienna fs = append(fs, func() { fmt.Println(v) }) }
Historia
W projekcie dynamicznego routingu używano pętli do tworzenia wielu handlerów poprzez zamknięcia, z których każdy miał przechwycić swoją ścieżkę. W rezultacie wszystkie handlery drukowały ostatnią ścieżkę — nie stworzono oddzielnej zmiennej w każdym zamknięciu. Błąd został zauważony dopiero podczas integracji z API HTTP.
Historia
Podczas testowania równoległego dostępu przez gorutyny wewnątrz pętli zamknięcie przechwytywało referencję do indeksu, a nie kopię. Powodowało to "losowe" efekty: dane zapisywane były nie w swoim slocie tablicy, a w ostatnim.
Historia
W funkcji zbierania statystyk zamknięcie zmieniało wspólną zmienną z zewnętrznego kontekstu, chociaż autor oczekiwał niezależnego licznika dla każdego zadania. Problem zauważono po nieadekwatnie rekonstruowanej sumie, która zawsze była wspólna, a nie prywatna, pomimo lokalnej logiki.