En Go, les fonctions anonymes (littéraux de fonction, fermetures) sont apparues pour soutenir le style fonctionnel, les rappels et l'encapsulation concise des algorithmes. Elles sont souvent utilisées pour traiter des collections, des tâches asynchrones et être transmises comme paramètres.
Sans les fonctions anonymes, le code devient redondant : chaque traitement doit être extrait dans une fonction nommée séparée. Mais cela entraine des questions : comment fonctionne la "captation" des variables, où est stockée la mémoire, quelles sont les particularités lors de la déclaration et de la transmission comme arguments ? La capture des variables est-elle protégée si elles sont modifiées de l'extérieur ? Une erreur fréquente est la capture incorrecte d'une variable dans une boucle.
Les fonctions anonymes sont déclarées comme des littéraux et peuvent être assignées à des variables ou utilisées directement. Si une fonction anonyme accède à des variables d'une portée externe, elles sont "captées" et conservées pour la durée de la fermeture. En tant que paramètre d'une fonction, une fonction anonyme est généralement transmise avec un type func, compatible avec la signature. La plupart des problèmes surviennent lors de la capture des variables de boucle — ici, si vous devez encapsuler la logique dans une fermeture, créez toujours une nouvelle variable à l'intérieur de la boucle.
Exemple de code :
func operate(nums []int, op func(int) int) []int { res := make([]int, len(nums)) for i, n := range nums { res[i] = op(n) } return res } func main() { arr := []int{1, 2, 3} out := operate(arr, func(x int) int {return x * x}) fmt.Println(out) // [1 4 9] }
Points clés :
Que se passera-t-il si vous captez une variable avec une valeur mutable dans une boucle via une fonction anonyme ?
Toutes les fermetures captureront la même variable, et lors de l'appel de la fonction après la sortie de la boucle, vous obtiendrez la même valeur.
Exemple de code :
func main() { a := []func(){} for i := 0; i < 3; i++ { a = append(a, func() { fmt.Println(i) }) } for _, f := range a { f() } // 3 3 3 }
Pour éviter cela, créez une nouvelle variable dans le corps de la boucle :
for i := 0; i < 3; i++ { j := i a = append(a, func() { fmt.Println(j) }) // 0 1 2 }
Peut-on utiliser des fonctions anonymes comme valeurs de type interface{} ?
Les fonctions ne sont compatibles qu'avec interface{}, pas avec d'autres interfaces, elles ne peuvent pas être comparées entre elles (sauf nil). Si vous transmettez une fermeture comme interface{}, vous ne pouvez l'appeler qu'en la castant au type de signature func.
Les fonctions anonymes peuvent-elles être récursives ?
Oui, mais seulement si vous déclarez d'abord une variable-nom pour la fermeture, puis assignez la fonction à elle-même.
Exemple de code :
var fib func(n int) int fib = func(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) } fmt.Println(fib(10)) // 55
Dans une boucle sur une liste de rappels, le développeur attache un gestionnaire comme fermeture avec la captation de la variable-itérateur. Tous les rappels travaillent avec une valeur incorrecte, entraînant des bogues.
Avantages :
Inconvénients :
À l'intérieur de la boucle, une nouvelle variable est créée pour chaque fermeture, garantissant une captation correcte de la valeur et un comportement attendu.
Avantages :
Inconvénients :