range é a forma do for que percorre uma coleção. Ele entrega um ou dois valores por iteração, e o que esses valores são depende do tipo que você percorre:
| Operando | Primeiro valor | Segundo valor | Observações |
|---|---|---|---|
slice []T, array [n]T | índice int | elemento T, uma cópia | |
ponteiro para array *[n]T | índice | elemento | |
| string | índice em bytes int | rune | decodifica UTF-8 |
map map[K]V | chave K | valor V | ordem aleatória |
channel chan T | elemento T | nenhum | até o channel ser fechado |
inteiro n | de 0 a n menos 1 | nenhum | Go 1.22 |
func(yield func() bool) | nenhum | nenhum | Go 1.23 |
func(yield func(V) bool) | V | nenhum | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Escolhendo quais variáveis receber
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
Declarar uma variável que você não usa é erro de compilação, então omita ou descarte com _ as que não precisar. for range 3 {} é o jeito mais curto de repetir algo três vezes.
O valor é uma cópia
A cada iteração, o elemento é copiado para a variável de valor. Mudar a variável não muda a coleção:
Copiar também custa tempo quando os elementos são structs grandes. Para um slice de structs grandes, for i := range s com s[i] evita a cópia.
Strings entregam runes
Percorrer uma string com range decodifica o UTF-8. O primeiro valor é o offset em bytes onde cada caractere começa, então ele pode pular números:
Para percorrer bytes em vez de caracteres, use um laço por índice, for i := 0; i < len(s); i++, ou faça range sobre []byte(s). Veja runes e bytes para os detalhes do UTF-8.
Maps: ordem aleatória
A ordem de iteração de um map não é especificada, e o runtime a embaralha para que o código não passe a depender de uma ordem. Para uma saída determinística, ordene as chaves:
Apagar entradas durante o range é seguro, e uma entrada apagada que ainda não foi alcançada não é entregue. Acrescentar entradas é permitido, mas imprevisível: uma chave nova pode ou não aparecer mais tarde no mesmo laço. fmt.Println de um map imprime as chaves ordenadas, e é por isso que a última linha é estável.
Channels: até serem fechados
Percorrer um channel com range recebe valores até que o channel seja fechado e esvaziado. Se ninguém fechá-lo, o laço bloqueia para sempre (e, se todas as goroutines estiverem bloqueadas, o runtime para com fatal error: all goroutines are asleep - deadlock!):
Quem envia fecha o channel, nunca quem recebe. Veja channels para channels com buffer e select.
Inteiros (Go 1.22)
range n conta de 0 até n, sem incluir n. O tipo da variável do laço é o tipo 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
Um n negativo também executa zero vezes. n é avaliado uma vez, antes de o laço começar.
O que o range avalia, e quando
A expressão depois de range é avaliada uma vez, antes da primeira iteração. Para um slice, o range passa a usar aquele cabeçalho de slice, então o tamanho fica fixo:
O 99 acrescentado nunca é visitado porque o tamanho do laço ficou fixo em 3. Mas nums[2] = 30 é visto, porque nesse momento nums ainda compartilha o array subjacente com o slice que está sendo percorrido. Troque as duas linhas de lugar e o 30 também some: o append passa da capacidade de 3, aloca um array novo, e a escrita cai nele. Percorrer um array por valor copia o array inteiro antes, então a mudança em arr[2] não é vista; faça range sobre &arr ou arr[:] para evitar a cópia.
range sobre funções (Go 1.23)
Desde o Go 1.23, o range também aceita funções iteradoras. Um iterador recebe um callback yield, chama-o uma vez por valor e para quando yield devolve false (o que acontece quando o corpo do laço executa break ou return):
iter.Seq[V] e iter.Seq2[K, V] são os nomes padrão dos dois formatos de iterador. A biblioteca padrão os devolve em slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All e, no Go 1.24, em strings.SplitSeq, strings.Lines e bytes.SplitSeq. O iterador precisa respeitar o valor devolvido por yield: chamar yield de novo depois que ele devolveu false causa panic.
Variáveis de laço por iteração (Go 1.22)
Desde o Go 1.22, as variáveis declaradas por for ... range são novas a cada iteração. Capturar v em uma closure ou goroutine captura o valor daquela iteração, e não uma variável compartilhada que acaba guardando o último elemento. Antes do Go 1.22 você precisava de v := v dentro do laço; essa linha agora é redundante. A regra é definida pela versão go do go.mod, então um módulo antigo mantém o comportamento antigo.
Erros comuns
- Pegar o endereço da variável de valor. Antes do Go 1.22,
ptrs = append(ptrs, &v)guardava o mesmo endereço toda vez. Desde o 1.22 guarda endereços distintos, mas cada um aponta para uma cópia, não para o elemento do slice. Use&items[i]para apontar para dentro do slice. - Esperar ordem em map. Uma saída que parece ordenada em um teste pequeno não vai continuar ordenada.
- Fazer range sobre um channel que ninguém fecha. O laço, e a goroutine que o executa, esperam para sempre.
- Fazer range sobre um array grande por valor. O array inteiro é copiado antes. Faça range sobre um slice dele.
Perguntas frequentes
O que o range devolve em Go?
Depende do operando. Para um slice ou array: índice e elemento. Para uma string: índice em bytes e rune. Para um map: chave e valor. Para um channel: cada valor recebido. Para um inteiro n (Go 1.22): de 0 a n menos 1. Para uma função iteradora (Go 1.23): o que a função entregar. Você pode omitir a segunda variável, ou descartar a primeira com _.
Por que alterar o valor do range não muda o meu slice?
A variável de valor é uma cópia do elemento. for _, v := range items { v.Price = 0 } altera só a cópia. Use o índice: for i := range items { items[i].Price = 0 }, ou percorra um slice de ponteiros.
Dá para usar range sobre um inteiro em Go?
Sim, desde o Go 1.22: for i := range 5 { ... } executa com i de 0 a 4. for range 5 { ... } repete cinco vezes sem variável. O go.mod do módulo precisa dizer go 1.22 ou mais recente.
É seguro apagar de um map enquanto se percorre com range?
Sim. Apagar uma entrada que ainda não foi alcançada faz com que ela não seja entregue, e apagar a entrada atual não tem problema. Acrescentar entradas durante o laço também é permitido, mas uma entrada nova pode ou não ser visitada, então não dependa disso.