ProgrammationDéveloppeur backend Go Middle/Senior

Comment fonctionnent les boucles for-range sur les slices et les maps en Go, et quelles sont les particularités de la copie de valeurs qui surviennent lors de leur utilisation ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Contexte de la question :

La construction for-range est apparue en Go comme un moyen d'itérer sur des collections (slices, tableaux, maps, chaînes). Les développeurs de Go ont introduit une optimisation : à chaque itération de la boucle, une copie de la valeur est effectuée au lieu d'utiliser directement la référence, ce qui peut conduire à des erreurs non évidentes, en particulier avec les variables de boucle.

Problème :

Beaucoup d'utilisateurs se trompent en essayant de prendre l'adresse d'une variable à l'intérieur du range (par exemple, &v), croyant qu'ils obtiennent l'adresse d'un élément de la collection, alors qu'en réalité, ils obtiennent l'adresse d'une variable locale.

Solution :

Dans la boucle for-range, à chaque itération, de nouvelles copies des variables d'itération (key, value) sont créées. Pour les types simples, cela ne pose pas problème, mais pour les structures, cela entraîne des surprises lors de la conservation d'un pointeur sur l'élément — il pointera toujours sur la même variable, et non sur des éléments différents du slice.

Exemple de code :

people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // tous les ptrs pointeront sur le même p }

Caractéristiques clés :

  • À chaque itération de la boucle, de nouvelles copies des variables key/value sont créées.
  • Prendre l'adresse de value à l'intérieur de for-range renvoie non pas l'adresse de l'élément dans la collection, mais l'adresse d'une variable temporaire.
  • Pour les maps, l'itération se fait dans un ordre aléatoire, il n'y a aucune garantie de séquence.

Questions pièges.

Que se passera-t-il lors de la conservation des références à la variable value à l'intérieur du range ?

Toutes les références pointeront vers la même mémoire, car value est une variable temporaire.

for _, v := range someSlice { ptrs = append(ptrs, &v) } // Tous les ptrs contiennent une référence vers la même variable !

Peut-on modifier un élément de la collection par référence via value dans range ?

Non, modifier value ne touche pas l'élément original de la collection. Pour modifier, il est nécessaire d'accéder par index.

for _, v := range arr { v.Field = 10 // arr ne sera pas modifié } for i := range arr { arr[i].Field = 10 // correct }

for-range sur map garantit-il un ordre d'itération séquentiel ?

Non, l'ordre d'itération sur map en Go n'est pas défini et peut être différent à chaque exécution de l'application.

Erreurs typiques et anti-patterns

  • Utilisation de &value dans range dans le but de conserver des références à différents éléments.
  • Modification de la variable value au lieu de passer par l’index.
  • Attente d'un ordre d'itération séquentiel sur map.

Exemple de la vie réelle

Cas négatif

Un développeur essaie de sérialiser une liste de références aux éléments d'une structure via range et conserve &value dans un slice séparé. Le résultat est un slice avec des adresses identiques.

Avantages :

  • Concision de la boucle.

Inconvénients :

  • Toutes les références sont identiques, en changeant un élément, tous changent.

Cas positif

Ils itèrent par index et conservent le pointeur vers l'élément souhaité du tableau :

for i := range arr { ptrs = append(ptrs, &arr[i]) }

Avantages :

  • Chaque pointeur pointe vers un élément séparé du slice original.

Inconvénients :

  • La syntaxe est plus longue, mais le résultat est prévisible.