La boucle for ... range permet de parcourir facilement les éléments d'un slice, d'une map, d'un tableau ou d'une chaîne.
Exemple :
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
Subtilité clé :
Les variables i, v, k, etc. sont réutilisées à chaque itération ! Cela devient souvent une source de bogues lors du passage de celles-ci par référence à l'intérieur de la boucle ou lors du lancement de goroutines à l'intérieur de range.
Que se passe-t-il si, à l'intérieur de range sur un slice, une goroutine est lancée en capturant la variable d'itération ? Comment éviter l'erreur ?
Réponse : Une erreur typique se produit : au sein de la goroutine, les variables de boucle sont utilisées, qui, après la fin de la boucle, auront la dernière valeur. Pour éviter cela, il faut créer des copies locales :
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Histoire
Dans un projet, nous avons utilisé range pour remplir un slice à travers plusieurs goroutines, oubliant de faire des copies des variables de boucle. Toutes les goroutines ont imprimé la même valeur - la dernière du tableau, ce qui a fortement altéré la logique métier.
Histoire
Lors de l'utilisation de range sur une map, un pointeur vers la valeur a été enregistré dans un nouveau slice de pointeurs. En conséquence, tous les éléments du nouveau slice faisaient référence à la même variable - celle utilisée dans la boucle (copie de la valeur). Le bogue est apparu lors de la mise à jour de ces variables en dehors de la boucle (panic : adresse mémoire invalide ou données inattendues).
Histoire
Dans un outil interne, en utilisant range sur une chaîne, j'ai lancé des gestionnaires de sous-chaînes significatives, mais à chaque itération, j'ai obtenu un décalage en octets, et non en caractères Unicode. Résultat : traitement incorrect pour les chaînes Unicode, découpage incorrect des caractères.