En Go, la construction de boucle for peut inclure un bloc d'initialisation (init), une vérification de condition et une expression postfixe. Historiquement, ce mécanisme a été créé pour faciliter l'écriture de code et par habitude des langages semblables à C. Cependant, en Go, la portée de la variable de boucle (i) a des particularités qui influencent fortement le comportement à l'intérieur des fonctions imbriquées, des closures et des goroutines.
Problème — Lors de l'exécution de goroutines ou de closures à chaque itération de la boucle, un comportement inattendu survient souvent : la variable i n'est pas copiée mais "capturée" par référence, c'est-à-dire que la closure accède à la variable de boucle partagée, qui, après la fin de la boucle, prend la dernière valeur. Cela conduit à des résultats identiques dans toutes les goroutines/closures, bien que la logique ait pu présager autre chose.
Solution — Si vous devez transmettre la valeur de la variable pour chaque itération, utilisez une copie explicite de la variable (via une variable supplémentaire) ou passez-la comme argument dans la closure.
Exemple de code :
for i := 0; i < 3; i++ { go func(j int) { fmt.Println(j) }(i) // Correct ! Valeur copiée } for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() // Erreur : toutes les goroutines imprimeront 3 }
Caractéristiques clés :
La portée de la variable for change-t-elle lors de l'utilisation de break ou de continue ?
Non. La portée de la variable déclarée dans for est toujours limitée au bloc de cette boucle. Break ou continue ne font que interrompre l'itération en cours, mais ne "lancent" pas la variable à l'extérieur.
Peut-on capturer la variable déclarée dans la partie init de for, à l'intérieur d'une méthode en dehors de la boucle ?
Non. La variable n'est visible qu'à l'intérieur du for lui-même et de tous les blocs imbriqués, mais pas à l'extérieur après la fin de la boucle.
Que se passera-t-il si la capture de la variable se produit dans une expression defer à l'intérieur du for ?
Même situation : la fonction defer "verra" non pas la valeur au moment de la création, mais la valeur actuelle de la variable au moment de l'exécution du defer (généralement, la dernière valeur de la boucle).
for i := 0; i < 3; i++ { defer fmt.Println(i) // tous les defer imprimeront 3 }
Dans un serveur web Go, le développeur a lancé plusieurs goroutines pour traiter différents ports, en utilisant l'indice du port comme variable de boucle et en capturant directement celle-ci dans une expression lambda. Toutes les goroutines accédaient à un seul port — le dernier du tableau.
Avantages :
Inconvénients :
L'équipe a introduit une règle — toujours copier la valeur de la variable de boucle dans une nouvelle variable, qui sera ensuite capturée par la closure/goroutine.
Avantages :
Inconvénients :