ProgrammationDéveloppeur Fullstack

Comment les fonctions anonymes sont-elles mises en œuvre et utilisées en Go, et quelles sont les particularités de leur utilisation en tant que paramètres de fonctions ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

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.

Problème

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.

Solution

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 :

  • Tout littéral func en Go peut capturer des variables de la portée externe.
  • La fermeture "vit" tant qu'il existe une référence vers elle ou qu'elle est utilisée, même après sortie de la portée d'origine.
  • La transmission d'une fonction anonyme comme paramètre est simplement effectuée avec le type func.

Questions piégées.

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

Erreurs typiques et anti-modèles

  • Captation de la variable de la boucle sans variable locale supplémentaire
  • Transmission de la fermeture avec un grand nombre d'objets captés sur le tas sans besoin explicite
  • Utilisation de fonctions anonymes en dehors du contexte, si la lisibilité et la réutilisation du code sont requises

Exemple de la vie réelle

Cas négatif

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 :

  • Minimum de code d шаблона

Inconvénients :

  • Erreur fréquente avec la valeur de la boucle, difficile à identifier.

Cas positif

À 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 :

  • Suivre simplement les recommandations permet d'éviter les bogues.
  • Le code est concis et sûr.

Inconvénients :

  • Nécessite de connaître les subtilités des fermetures et des portées