range est la forme de for qui parcourt une collection. Elle vous donne une ou deux valeurs par itération, et ce qu'elles sont dépend du type parcouru :
| Opérande | Première valeur | Deuxième valeur | Remarques |
|---|---|---|---|
slice []T, tableau [n]T | indice int | élément T, une copie | |
pointeur vers un tableau *[n]T | indice | élément | |
| chaîne | indice d'octet int | rune | décode l'UTF-8 |
map map[K]V | clé K | valeur V | ordre aléatoire |
channel chan T | élément T | aucune | jusqu'à la fermeture du channel |
entier n | 0 à n moins 1 | aucune | Go 1.22 |
func(yield func() bool) | aucune | aucune | Go 1.23 |
func(yield func(V) bool) | V | aucune | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Choisir les variables que vous recevez
for i, v := range s {} // both
for i := range s {} // index (or key) only
for _, v := range s {} // value only
for range s {} // neither: just repeat len(s) times
Déclarer une variable que vous n'utilisez pas est une erreur de compilation, donc omettez ou remplacez par l'identifiant blanc celles dont vous n'avez pas besoin. for range 3 {} est la façon la plus courte de répéter quelque chose trois fois.
La valeur est une copie
À chaque itération, l'élément est copié dans la variable valeur. Modifier la variable ne modifie pas la collection :
La copie coûte aussi du temps quand les éléments sont de grosses structs. Pour une slice de grosses structs, for i := range s avec s[i] évite la copie.
Les chaînes fournissent des runes
Parcourir une chaîne décode l'UTF-8. La première valeur est la position en octets où commence chaque caractère, donc elle peut sauter des nombres :
Pour parcourir des octets plutôt que des caractères, utilisez une boucle à indice, for i := 0; i < len(s); i++, ou parcourez []byte(s). Voir runes et octets pour les détails sur l'UTF-8.
Maps : ordre aléatoire
L'ordre d'itération d'une map n'est pas spécifié, et le runtime le rend aléatoire pour que le code ne puisse pas finir par dépendre d'un ordre. Pour une sortie déterministe, triez les clés :
Supprimer des entrées pendant le parcours est sûr, et une entrée supprimée pas encore atteinte n'est pas produite. Ajouter des entrées est permis mais imprévisible : une nouvelle clé peut apparaître ou non plus loin dans la même boucle. fmt.Println d'une map affiche les clés triées, c'est pourquoi la dernière ligne est stable.
Channels : jusqu'à la fermeture
Parcourir un channel reçoit des valeurs jusqu'à ce que le channel soit fermé et vidé. Si personne ne le ferme, la boucle bloque indéfiniment (et si toutes les goroutines sont bloquées, le runtime s'arrête avec fatal error: all goroutines are asleep - deadlock!) :
C'est l'émetteur qui ferme le channel, jamais le récepteur. Voir les channels pour les channels avec buffer et select.
Entiers (Go 1.22)
range n compte de 0 jusqu'à n exclu. Le type de la variable de boucle est le type de n :
for i := range 3 {} // i is int: 0, 1, 2
for i := range uint8(3) {} // i is uint8
for range 0 {} // runs zero times
Un n négatif s'exécute aussi zéro fois. n est évalué une seule fois, avant le début de la boucle.
Ce que range évalue, et quand
L'expression après range est évaluée une seule fois, avant la première itération. Pour une slice, range utilise ensuite cet en-tête de slice, donc sa longueur est fixe :
Le 99 ajouté n'est jamais visité parce que la longueur de la boucle a été fixée à 3. Mais nums[2] = 30 est vu, parce qu'à ce moment nums partage encore son tableau sous-jacent avec la slice parcourue. Inversez les deux lignes et le 30 disparaît aussi : l'append dépasse la capacité de 3, alloue un nouveau tableau, et l'écriture y atterrit. Parcourir une valeur tableau copie d'abord tout le tableau, donc la modification de arr[2] n'est pas vue ; parcourez &arr ou arr[:] pour éviter la copie.
Range sur des fonctions (Go 1.23)
Depuis Go 1.23, range accepte aussi des fonctions itérateurs. Un itérateur prend un callback yield, l'appelle une fois par valeur, et s'arrête quand yield renvoie false (ce qui arrive quand le corps de la boucle exécute break ou return) :
iter.Seq[V] et iter.Seq2[K, V] sont les noms standard des deux formes d'itérateurs. La bibliothèque standard les renvoie depuis slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All, et avec Go 1.24 depuis strings.SplitSeq, strings.Lines et bytes.SplitSeq. L'itérateur doit respecter la valeur de retour de yield : rappeler yield après qu'il a renvoyé false provoque un panic.
Les variables de boucle sont propres à chaque itération (Go 1.22)
Depuis Go 1.22, les variables déclarées par for ... range sont nouvelles à chaque itération. Capturer v dans une closure ou une goroutine capture la valeur de cette itération, pas une variable partagée qui finit par contenir le dernier élément. Avant Go 1.22, il fallait v := v dans la boucle ; cette ligne est désormais redondante. La règle est fixée par la version go du go.mod, donc un ancien module garde l'ancien comportement.
Erreurs courantes
- Prendre l'adresse de la variable valeur. Avant Go 1.22,
ptrs = append(ptrs, &v)stockait la même adresse à chaque fois. Depuis 1.22, les adresses sont distinctes, mais chacune pointe vers une copie, pas vers l'élément de la slice. Utilisez&items[i]pour pointer dans la slice. - S'attendre à un ordre dans les maps. Une sortie qui paraît triée dans un petit test ne le restera pas.
- Parcourir un channel que personne ne ferme. La boucle, et la goroutine qui l'exécute, attendent indéfiniment.
- Parcourir un gros tableau par valeur. Tout le tableau est d'abord copié. Parcourez plutôt une slice de celui-ci.
Questions fréquentes
Que renvoie range en Go ?
Cela dépend de l'opérande. Pour une slice ou un tableau : l'indice et l'élément. Pour une chaîne : l'indice d'octet et la rune. Pour une map : la clé et la valeur. Pour un channel : chaque valeur reçue. Pour un entier n (Go 1.22) : de 0 à n moins 1. Pour une fonction itérateur (Go 1.23) : ce que la fonction fournit. Vous pouvez omettre la seconde variable, ou ignorer la première avec _.
Pourquoi modifier la valeur de range ne change-t-il pas ma slice ?
La variable valeur est une copie de l'élément. for _, v := range items { v.Price = 0 } ne modifie que la copie. Utilisez plutôt l'indice : for i := range items { items[i].Price = 0 }, ou parcourez une slice de pointeurs.
Peut-on faire un range sur un entier en Go ?
Oui, depuis Go 1.22 : for i := range 5 { ... } s'exécute avec i de 0 à 4. for range 5 { ... } répète cinq fois sans variable. Le go.mod du module doit indiquer go 1.22 ou plus.
Peut-on supprimer d'une map pendant qu'on la parcourt ?
Oui. Supprimer une entrée pas encore atteinte signifie qu'elle ne sera pas produite, et supprimer l'entrée courante ne pose pas de problème. Ajouter des entrées pendant la boucle est aussi permis, mais une nouvelle entrée peut être visitée ou non, donc n'en dépendez pas.