range è la forma di for che percorre una collezione. Ti dà uno o due valori per iterazione, e quali siano dipende dal tipo su cui fai il range:
| Operando | Primo valore | Secondo valore | Note |
|---|---|---|---|
slice []T, array [n]T | indice int | elemento T, una copia | |
puntatore ad array *[n]T | indice | elemento | |
| stringa | indice in byte int | rune | decodifica UTF-8 |
map map[K]V | chiave K | valore V | ordine casuale |
channel chan T | elemento T | nessuno | finché il channel non viene chiuso |
intero n | da 0 a n meno 1 | nessuno | Go 1.22 |
func(yield func() bool) | nessuno | nessuno | Go 1.23 |
func(yield func(V) bool) | V | nessuno | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Scegliere quali variabili ricevere
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
Dichiarare una variabile che non usi è un errore di compilazione, quindi togli o sostituisci con _ quelle che non ti servono. for range 3 {} è il modo più corto per ripetere qualcosa tre volte.
Il valore è una copia
A ogni iterazione, l'elemento viene copiato nella variabile del valore. Cambiare la variabile non cambia la collezione:
Copiare costa anche tempo quando gli elementi sono struct grandi. Per una slice di struct grandi, for i := range s con s[i] evita la copia.
Le stringhe producono rune
Il range su una stringa decodifica l'UTF-8. Il primo valore è l'offset in byte dove inizia ogni carattere, quindi può saltare dei numeri:
Per percorrere i byte invece dei caratteri, usa un ciclo con indice, for i := 0; i < len(s); i++, o fai il range su []byte(s). Vedi rune e byte per i dettagli sull'UTF-8.
Map: ordine casuale
L'ordine di iterazione di una map non è specificato, e il runtime lo rende casuale così il codice non può finire per dipendere da un ordine. Per un output deterministico, ordina le chiavi:
Eliminare elementi durante il range è sicuro, e un elemento eliminato non ancora raggiunto non viene prodotto. Aggiungere elementi è permesso ma imprevedibile: una nuova chiave potrebbe comparire più avanti nello stesso ciclo oppure no. fmt.Println su una map stampa le chiavi ordinate, ed è per questo che l'ultima riga è stabile.
Channel: finché non viene chiuso
Il range su un channel riceve valori finché il channel non viene chiuso e svuotato. Se nessuno lo chiude, il ciclo si blocca per sempre (e se tutte le goroutine sono bloccate, il runtime si ferma con fatal error: all goroutines are asleep - deadlock!):
È chi invia a chiudere il channel, mai chi riceve. Vedi channel per i channel bufferizzati e select.
Interi (Go 1.22)
range n conta da 0 fino a n escluso. Il tipo della variabile del ciclo è il tipo di 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
Anche un n negativo gira zero volte. n viene valutato una volta sola, prima che il ciclo inizi.
Cosa valuta range, e quando
L'espressione dopo range viene valutata una volta, prima della prima iterazione. Per una slice, range usa poi quell'header della slice, quindi la sua lunghezza è fissa:
Il 99 aggiunto non viene mai visitato perché la lunghezza del ciclo era fissata a 3. Ma nums[2] = 30 si vede, perché in quel momento nums condivide ancora l'array sottostante con la slice su cui si fa il range. Scambia le due righe e sparisce anche il 30: l'append supera la capacità di 3, alloca un nuovo array, e la scrittura finisce lì. Il range su un valore array copia prima l'intero array, quindi la modifica ad arr[2] non si vede; fai il range su &arr o su arr[:] per evitare la copia.
Range su funzioni (Go 1.23)
Da Go 1.23, range accetta anche funzioni iteratore. Un iteratore riceve una callback yield, la chiama una volta per ogni valore e si ferma quando yield restituisce false (cosa che succede quando il corpo del ciclo esegue break o return):
iter.Seq[V] e iter.Seq2[K, V] sono i nomi standard delle due forme di iteratore. La libreria standard li restituisce da slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All, e in Go 1.24 da strings.SplitSeq, strings.Lines e bytes.SplitSeq. L'iteratore deve rispettare il valore restituito da yield: chiamare di nuovo yield dopo che ha restituito false va in panic.
Le variabili del ciclo sono per iterazione (Go 1.22)
Da Go 1.22, le variabili dichiarate da for ... range sono nuove a ogni iterazione. Catturare v in una closure o in una goroutine cattura il valore di quella iterazione, non una variabile condivisa che finisce per contenere l'ultimo elemento. Prima di Go 1.22 serviva v := v dentro il ciclo; ora quella riga è superflua. La regola dipende dalla versione go in go.mod, quindi un modulo vecchio mantiene il comportamento vecchio.
Errori comuni
- Prendere l'indirizzo della variabile del valore. Prima di Go 1.22,
ptrs = append(ptrs, &v)memorizzava ogni volta lo stesso indirizzo. Dalla 1.22 memorizza indirizzi distinti, ma ognuno punta a una copia, non all'elemento della slice. Usa&items[i]per puntare dentro la slice. - Aspettarsi un ordine nelle map. Un output che sembra ordinato in un piccolo test non resterà ordinato.
- Fare il range su un channel che nessuno chiude. Il ciclo, e la goroutine che lo esegue, aspettano per sempre.
- Fare il range su un array grande per valore. Prima viene copiato l'intero array. Fai invece il range su una sua slice.
Domande frequenti
Cosa restituisce range in Go?
Dipende dall'operando. Per una slice o un array: indice ed elemento. Per una stringa: indice in byte e rune. Per una map: chiave e valore. Per un channel: ogni valore ricevuto. Per un intero n (Go 1.22): da 0 a n meno 1. Per una funzione iteratore (Go 1.23): quello che la funzione produce. Puoi omettere la seconda variabile, o scartare la prima con _.
Perché modificare il valore del range non cambia la mia slice?
La variabile del valore è una copia dell'elemento. for _, v := range items { v.Price = 0 } cambia solo la copia. Usa invece l'indice: for i := range items { items[i].Price = 0 }, oppure fai il range su una slice di puntatori.
Posso fare il range su un intero in Go?
Sì, da Go 1.22: for i := range 5 { ... } gira con i da 0 a 4. for range 5 { ... } ripete cinque volte senza variabile. Il go.mod del modulo deve dire go 1.22 o successivo.
È sicuro eliminare da una map durante il range?
Sì. Eliminare un elemento non ancora raggiunto significa che non verrà prodotto, ed eliminare l'elemento corrente va bene. Anche aggiungere elementi durante il ciclo è permesso, ma un nuovo elemento potrebbe essere visitato oppure no, quindi non contarci.