En Go, les fonctions anonymes (fonctions littérales) sont capables de créer des fermetures, c'est-à-dire d'accéder aux variables de l'environnement même après la fin de celui-ci. De telles fermetures allouent de la mémoire dans le tas si nécessaire pour un fonctionnement correct (détecté via l'analyse d'échappement).
Exemple :
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d ", a(5)) // 5 printf("%d ", a(10)) // 15
Particularités :
Que va imprimer ce code ?
func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }
Beaucoup diront qu'il imprimera 0, 1, 2, mais le résultat sera :
3
3
3
Toutes les fermetures pointent vers la même variable i ; après la fin de la boucle, sa valeur est 3.
Correct : capturer une copie de la variable dans le corps de la boucle :
for i := 0; i < 3; i++ { v := i // nouvelle variable fs = append(fs, func() { fmt.Println(v) }) }
Histoire
Dans un projet de routage dynamique, nous utilisions une boucle pour créer de nombreux gestionnaires via des fermetures, chacun devait capturer son propre chemin. En conséquence, tous les gestionnaires imprimaient le dernier chemin — nous n'avions pas créé de variable séparée dans chaque fermeture. L'erreur n'a été détectée qu'au moment de l'intégration avec l'API HTTP.
Histoire
Lors des tests d'accès parallèle via des goroutines, la fermeture capturait la référence à l'index, et non la copie. Cela créait des effets « aléatoires » : les données étaient écrites non dans leur slot de tableau, mais dans le dernier.
Histoire
Dans une fonction de collecte de statistiques, la fermeture modifiait une variable partagée de l'environnement externe, bien que l'auteur s'attendait à un compteur indépendant pour chaque tâche. Le problème a été remarqué par la somme de reconstruction inadéquate, qui était toujours globale, pas locale, malgré la logique locale.