Carregando
Todos os artigos
Programação 18 min de leitura

O Laço For em Go: Um Guia Visual e Prático

O Go tem uma única palavra-chave de laço, e ela faz tudo. Aprenda o for clássico de três partes, as formas while e infinita, range em slices, strings, maps e inteiros, break e continue, a mudança das variáveis de laço do Go 1.22, armadilhas, medições reais e exercícios.

nbkhope
Nesta página

A maioria das linguagens dá vários laços: for, while, do-while, foreach. O Go dá exatamente uma palavra-chave, o for, e a molda para cada tarefa: contar, repetir enquanto uma condição vale, repetir para sempre até um break e percorrer slices, strings e maps com o range.

Neste guia você vai escrever todas as formas do for do Go, acompanhar um laço passo a passo, conhecer o break, o continue e os rótulos, e cair nas armadilhas clássicas, incluindo um erro que o próprio Go consertou na versão 1.22 e que você vai reproduzir mudando uma única linha do go.mod. Medições reais e exercícios fecham o artigo.

Todas as saídas abaixo vêm de execuções reais com o Go 1.22 no Windows.

O for clássico de três partes

1package main
2
3import "fmt"
4
5func main() {
6	for contagem := 1; contagem <= 5; contagem++ {
7		fmt.Println("Contagem:", contagem)
8	}
9	fmt.Println("Pronto!")
10}
1Contagem: 1
2Contagem: 2
3Contagem: 3
4Contagem: 4
5Contagem: 5
6Pronto!
A anatomia do for de três partes do Go. for contagem := 1; contagem <= 5; contagem++ { … }. O início, contagem := 1, roda uma vez antes de o laço começar e declara a variável do laço. A condição, contagem <= 5, é conferida antes de cada volta: se for verdadeira o corpo roda, se for falsa o laço termina. O passo, contagem++, roda depois de cada volta. O corpo entre as chaves roda uma vez por volta. O Go não usa parênteses em volta das três partes, e as chaves são obrigatórias mesmo para um corpo de uma linha

O cabeçalho tem três partes separadas por ponto e vírgula:

  1. Início (contagem := 1): roda uma vez, antes de tudo. Normalmente declara a variável do laço.
  2. Condição (contagem <= 5): conferida antes de cada volta. Verdadeira roda o corpo; falsa termina o laço.
  3. Passo (contagem++): roda depois de cada volta.

Ao contrário do C, do C++ e do Java, o Go não usa parênteses em volta das três partes, e as chaves são obrigatórias, mesmo para um corpo de uma linha.

Passo a passo

Acompanhando o laço for contagem := 1; contagem <= 5; contagem++. O início põe 1 na contagem. Confere 1 <= 5: verdadeiro, imprime Contagem: 1, e o contagem++ a deixa em 2. Confere 2 <= 5: verdadeiro, imprime Contagem: 2, a contagem vira 3. O mesmo acontece com 3, 4 e 5. Aí o contagem++ deixa a contagem em 6, e a conferência 6 <= 5 é falsa, então o laço termina depois de 5 voltas e 6 conferências, e o programa imprime Pronto!. A variável contagem só existe dentro do laço

A condição é conferida 6 vezes para 5 voltas: a última conferência (6 <= 5) é a que para o laço. E a contagem só existe dentro do laço; usá-la depois da chave de fechamento é um erro de compilação (undefined: contagem).

Contando de outros jeitos

O passo pode fazer qualquer coisa, não só somar um:

1package main
2
3import "fmt"
4
5func main() {
6	for regressiva := 5; regressiva > 0; regressiva-- {
7		fmt.Print(regressiva, " ")
8	}
9	fmt.Println("Decolar!")
10
11	for par := 0; par <= 10; par += 2 {
12		fmt.Print(par, " ")
13	}
14	fmt.Println()
15
16	for potencia := 1; potencia < 1000; potencia *= 2 {
17		fmt.Print(potencia, " ")
18	}
19	fmt.Println()
20}
5 4 3 2 1 Decolar!
0 2 4 6 8 10 
1 2 4 8 16 32 64 128 256 512 

Uma palavra-chave, quatro formas

Uma palavra-chave de laço, quatro formas. 1, Três partes: for contagem := 1; contagem <= 5; contagem++ { }, para contar. 2, Só a condição: for total < 100 { }, o while do Go. 3, Sem condição: for { … break … }, repete para sempre até um break ou return. 4, Range: for indice, valor := range colecao { }, percorre um slice, array, string, map, canal ou, desde o Go 1.22, um inteiro. O Go não tem as palavras-chave while nem do-while: o for faz tudo

Só a condição: o while do Go

Tire o início e o passo, e o for vira um laço while ("enquanto"):

1package main
2
3import "fmt"
4
5func main() {
6	poupanca := 100.0
7	anos := 0
8	for poupanca < 200 {
9		poupanca *= 1.07
10		anos++
11	}
12	fmt.Printf("Dobrou para %.2f depois de %d anos a 7%%\n", poupanca, anos)
13}
Dobrou para 210.49 depois de 11 anos a 7%

Use essa forma quando você não sabe de antemão quantas voltas vai precisar.

Sem condição: repita até o break

O for { } repete para sempre. Você sai com break (ou return):

1package main
2
3import "fmt"
4
5func main() {
6	numero := 27
7	passos := 0
8	for {
9		if numero == 1 {
10			break
11		}
12		if numero%2 == 0 {
13			numero /= 2
14		} else {
15			numero = 3*numero + 1
16		}
17		passos++
18	}
19	fmt.Println("Chegou a 1 depois de", passos, "passos")
20}
Chegou a 1 depois de 111 passos

Essa é a famosa sequência de Collatz: divida os pares por dois, multiplique os ímpares por três e some um, e (até onde alguém já conferiu) você sempre chega a 1. Começar do 27 leva surpreendentes 111 passos. Um for { } também é como servidores e jogos rodam os seus laços principais.

range: percorrendo coleções

Slices

1package main
2
3import "fmt"
4
5func main() {
6	frutas := []string{"maçã", "banana", "cereja"}
7	for indice, fruta := range frutas {
8		fmt.Println(indice, fruta)
9	}
10	for _, fruta := range frutas {
11		fmt.Print(fruta, " ")
12	}
13	fmt.Println()
14	for indice := range frutas {
15		fmt.Print(indice, " ")
16	}
17	fmt.Println()
18}
10 maçã
21 banana
32 cereja
4maçã banana cereja 
50 1 2 

O range dá dois valores por volta: o índice e uma cópia do elemento. Não precisa do índice? Escreva _ (o identificador vazio), porque uma variável sem uso é erro de compilação. Precisa só do índice? Deixe a segunda variável de fora.

Inteiros (Go 1.22)

Desde o Go 1.22, o range funciona com um inteiro simples, contando do 0 até ele (sem incluí-lo):

1package main
2
3import "fmt"
4
5func main() {
6	for volta := range 3 {
7		fmt.Println("Volta", volta+1)
8	}
9}
Volta 1
Volta 2
Volta 3

for volta := range 3 é um jeito mais limpo de escrever for volta := 0; volta < 3; volta++.

Strings: caracteres, não bytes

1package main
2
3import "fmt"
4
5func main() {
6	for indice, caractere := range "Olá!" {
7		fmt.Printf("byte %d: %c\n", indice, caractere)
8	}
9}
1byte 0: O
2byte 1: l
3byte 2: á
4byte 4: !
O range numa string. A string "Olá!" tem 5 bytes em UTF-8: O no byte 0, l no byte 1, á ocupando os bytes 2 e 3, e ! no byte 4. Um laço for range entrega um rune, um caractere inteiro, por volta, junto com o índice do seu primeiro byte: 0 O, 1 l, 2 á, 4 !. O índice pula de 2 para 4 porque o á tem dois bytes. Um laço clássico com s[i] visitaria 5 bytes e partiria o á ao meio

O range numa string entrega um rune (um caractere Unicode inteiro) por volta, mais o índice do byte onde ele começa. Por isso o índice pula de 2 para 4: o á ocupa dois bytes. (O %c imprime um rune como caractere.) Um laço de índice clássico com texto[indice] visitaria bytes e cortaria o á ao meio, algo que importa muito em português.

Maps: em ordem nenhuma

1package main
2
3import (
4	"fmt"
5	"sort"
6)
7
8func main() {
9	capitais := map[string]string{"França": "Paris", "Japão": "Tóquio", "Brasil": "Brasília", "Quênia": "Nairóbi"}
10
11	paises := make([]string, 0, len(capitais))
12	for pais := range capitais {
13		paises = append(paises, pais)
14	}
15	sort.Strings(paises)
16
17	for _, pais := range paises {
18		fmt.Println(pais, "→", capitais[pais])
19	}
20}
1Brasil → Brasília
2França → Paris
3Japão → Tóquio
4Quênia → Nairóbi

O range num map entrega cada chave e valor, mas a ordem é aleatória de propósito, para os programas não passarem a depender dela. Um simples for pais, capital := range capitais { fmt.Println(pais, capital) } imprimiu isto em duas execuções seguidas:

1$ go run .
2Brasil Brasília
3Quênia Nairóbi
4França Paris
5Japão Tóquio
6$ go run .
7Japão Tóquio
8Brasil Brasília
9Quênia Nairóbi
10França Paris
Quão aleatória é a ordem de um map? Percorremos o mesmo map de 4 chaves 10.000 vezes num programa em Go 1.22 e anotamos a ordem. Só apareceram 4 ordens diferentes, e não todas as 24 possíveis: França, Japão, Brasil, Quênia: 6.242 vezes; Brasil, Quênia, França, Japão: 1.287 vezes; Japão, Brasil, Quênia, França: 1.263 vezes; Quênia, França, Japão, Brasil: 1.208 vezes. O Go escolhe um ponto de partida aleatório e percorre os compartimentos internos do map a partir dali, então as ordens são rotações. Nunca dependa da ordem de um map: quando a ordem importar, ordene as chaves antes

Percorremos esse map 10.000 vezes e contamos as ordens: só apareceram 4 ordens diferentes (rotações de uma mesma sequência, porque o Go escolhe um ponto de partida aleatório), e uma delas saiu em 62% das vezes. De qualquer jeito: quando a ordem importar, junte as chaves e ordene, como acima.

break, continue e rótulos

1package main
2
3import "fmt"
4
5func main() {
6	for numero := 1; numero <= 10; numero++ {
7		if numero%3 == 0 {
8			continue
9		}
10		if numero > 8 {
11			break
12		}
13		fmt.Print(numero, " ")
14	}
15	fmt.Println()
16
17	alvo := 12
18busca:
19	for primeiro := 1; primeiro <= 6; primeiro++ {
20		for segundo := 1; segundo <= 6; segundo++ {
21			if primeiro*segundo == alvo {
22				fmt.Printf("%d x %d = %d\n", primeiro, segundo, alvo)
23				break busca
24			}
25		}
26	}
27}
1 2 4 5 7 8 
2 x 6 = 12
  • continue pula o resto desta volta e segue para a próxima (o 3 e o 6 são pulados).
  • break sai do laço de vez (no 9).
  • Rótulos: um break simples só sai do laço mais de dentro. Coloque um rótulo como busca: antes do laço de fora, e o break busca sai dos dois de uma vez, então paramos no primeiro par (2 × 6) em vez de achar também 3 × 4, 4 × 3 e 6 × 2.

Um laço ou uma fórmula?

Laços são o jeito óbvio de somar 1 + 2 + … + n:

1package main
2
3import "fmt"
4
5func main() {
6	limite := 100
7	total := 0
8	for numero := 1; numero <= limite; numero++ {
9		total += numero
10	}
11	fmt.Println("Laço:", total)
12	fmt.Println("Fórmula:", limite*(limite+1)/2)
13}
Laço: 5050
Fórmula: 5050

Ainda menino, Carl Friedrich Gauss teria descoberto o atalho: junte 1 com 100, 2 com 99 e assim por diante, 50 pares de 101. Medimos os dois para n = 10 milhões:

A velocidade dos laços em Go, medida com testing.Benchmark no Go 1.22, Windows. Somar um slice de 10 milhões de ints: um laço de índice levou 5,2 milissegundos e um laço com range 5,6 milissegundos, iguais dentro do ruído da medição; o que rodava primeiro saía mais rápido, então a ordem pesou mais que o estilo. Somar de 1 a 10 milhões com um laço levou 3,9 milissegundos; a fórmula n vezes n mais 1 dividido por 2 levou 0,7 nanossegundo, cerca de 5 milhões de vezes mais rápida. Laços são rápidos, cerca de meio nanossegundo por número, mas um algoritmo melhor vence qualquer laço
O quêTempo
Somar 10 milhões de itens de um slice, laço de índice5,2 ms
Somar 10 milhões de itens de um slice, laço com range5,6 ms
Somar de 1 a 10 milhões com um laço3,9 ms
Somar de 1 a 10 milhões com a fórmula0,7 ns

Duas lições. Laços de índice e com range têm a mesma velocidade (o que rodávamos primeiro saía na frente, então a diferença é ruído), então escolha o mais claro, normalmente o range. E um laço que gasta meio nanossegundo por número é rápido, mas uma ideia melhor é milhões de vezes mais rápida.

Armadilhas

Armadilha 1: o range entrega uma cópia

1package main
2
3import "fmt"
4
5func main() {
6	precos := []int{10, 20, 30}
7	for _, preco := range precos {
8		preco *= 2
9	}
10	fmt.Println(precos)
11
12	for indice := range precos {
13		precos[indice] *= 2
14	}
15	fmt.Println(precos)
16}
[10 20 30]
[20 40 60]

A variável preco é uma cópia de cada elemento, então mudá-la não muda nada no slice. Para modificar os elementos, atribua pelo índice.

Armadilha 2: variáveis de laço em closures (consertado no Go 1.22)

Este programa guarda três funções num laço e as chama depois:

1package main
2
3import "fmt"
4
5func main() {
6	var impressoras []func()
7	for i := 0; i < 3; i++ {
8		impressoras = append(impressoras, func() { fmt.Print(i, " ") })
9	}
10	for _, impressora := range impressoras {
11		impressora()
12	}
13	fmt.Println()
14}
0 1 2 

É o que você esperaria, e é o que o Go 1.22 em diante imprime. Mas a linha go do seu go.mod decide quais regras de laço valem. Troque de go 1.22 para go 1.21 e rode exatamente o mesmo código:

$ go run .
3 3 3 
A mudança das variáveis de laço no Go 1.22. Com go 1.21 no go.mod, um laço for tinha uma única variável compartilhada por todas as voltas: as três funções guardadas apontam para o mesmo i, que vale 3 quando elas rodam depois do laço, então imprimem 3 3 3. Com go 1.22 ou mais novo no go.mod, cada volta ganha a sua própria cópia novinha da variável: as funções guardam 0, 1 e 2, e imprimem 0 1 2. Os dois resultados são execuções reais do mesmo código com o Go 1.22.5, mudando só a linha go do go.mod. Goroutines iniciadas num laço tinham o mesmo erro, um dos mais comuns da história do Go

Antes do Go 1.22, o laço tinha um único i compartilhado por todas as voltas, então as três funções viam o valor final dele, 3. O mesmo erro atingia goroutines iniciadas dentro de laços, e era um dos enganos mais comuns em código Go, então o Go 1.22 mudou as regras: cada volta agora ganha a sua própria variável. (O i aqui é o contador simples de um exemplo curto; em laços mais longos, prefira nomes descritivos.) Se você mantém código antigo, confira a versão no go.mod dele.

Armadilha 3: um a mais ou a menos

for indice := 0; indice <= len(frutas); indice++ dá uma volta a mais e quebra com panic: runtime error: index out of range [3] with length 3 ("índice fora do intervalo"). Os índices válidos vão de 0 a len - 1, então a condição é <, não <=. Melhor ainda: use o range e deixe o Go cuidar dos limites.

Sua vez: pratique

Tente fazer estes antes de espiar as respostas. Rode cada um com go run ..

Exercício 1: Adivinhe a saída (aquecimento). O que isto imprime?

1package main
2
3import "fmt"
4
5func main() {
6	for numero := 10; numero > 0; numero -= 3 {
7		fmt.Print(numero, " ")
8	}
9	fmt.Println()
10	for indice, letra := range "Go!" {
11		fmt.Print(indice, string(letra), " ")
12	}
13	fmt.Println()
14}

Exercício 2: Tabuada. Imprima a tabuada do 7, de 7 × 1 a 7 × 10, uma linha cada, como 7 x 3 = 21.

Exercício 3: Some os pares. Some os números pares de 1 a 100 usando continue para pular os ímpares.

Exercício 4: FizzBuzz. Para os números de 1 a 15, imprima Fizz para múltiplos de 3, Buzz para múltiplos de 5, FizzBuzz para múltiplos dos dois e o número nos outros casos, tudo numa linha.

Exercício 5: Conte as vogais (desafio). Conte as vogais (a, e, i, o, u, incluindo as acentuadas como á e ê) em "A programação é divertida" usando range na string e strings.ContainsRune.

Quiz rápido

  1. Quantas palavras-chave de laço o Go tem?
  2. Como se escreve um laço while em Go?
  3. Que dois valores o range entrega num slice?
  4. Por que o range em "Olá" pula do índice 2 para o 4?
  5. O que mudou nas variáveis de laço no Go 1.22?

Respostas

Exercício 1: O primeiro laço conta de 3 em 3 para baixo até o número não ser mais maior que 0; o segundo entrega índices de bytes e caracteres:

1package main
2
3import "fmt"
4
5func main() {
6	for numero := 10; numero > 0; numero -= 3 {
7		fmt.Print(numero, " ")
8	}
9	fmt.Println()
10	for indice, letra := range "Go!" {
11		fmt.Print(indice, string(letra), " ")
12	}
13	fmt.Println()
14}
10 7 4 1 
0G 1o 2! 

Exercício 2: O range 10 conta de 0 a 9, então some 1:

1package main
2
3import "fmt"
4
5func main() {
6	for passo := range 10 {
7		multiplicador := passo + 1
8		fmt.Printf("7 x %d = %d\n", multiplicador, 7*multiplicador)
9	}
10}
17 x 1 = 7
27 x 2 = 14
37 x 3 = 21
47 x 4 = 28
57 x 5 = 35
67 x 6 = 42
77 x 7 = 49
87 x 8 = 56
97 x 9 = 63
107 x 10 = 70

Exercício 3: O continue pula os ímpares antes de serem somados:

1package main
2
3import "fmt"
4
5func main() {
6	total := 0
7	for numero := 1; numero <= 100; numero++ {
8		if numero%2 != 0 {
9			continue
10		}
11		total += numero
12	}
13	fmt.Println(total)
14}
2550

Exercício 4: Confira "os dois" primeiro, senão o 15 pararia no Fizz:

1package main
2
3import "fmt"
4
5func main() {
6	for numero := 1; numero <= 15; numero++ {
7		switch {
8		case numero%15 == 0:
9			fmt.Print("FizzBuzz ")
10		case numero%3 == 0:
11			fmt.Print("Fizz ")
12		case numero%5 == 0:
13			fmt.Print("Buzz ")
14		default:
15			fmt.Print(numero, " ")
16		}
17	}
18	fmt.Println()
19}
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 

(Um switch sem valor confere cada condição de case em ordem, um substituto arrumado para uma corrente de if/else if.)

Exercício 5: O range na string entrega caracteres inteiros, então o á e o é contam uma vez cada:

1package main
2
3import (
4	"fmt"
5	"strings"
6)
7
8func main() {
9	texto := "A programação é divertida"
10	vogais := "aeiouáéíóúâêôãõAEIOU"
11	contagem := 0
12	for _, caractere := range texto {
13		if strings.ContainsRune(vogais, caractere) {
14			contagem++
15		}
16	}
17	fmt.Println("Vogais:", contagem)
18}
Vogais: 11

Quiz: (1) Uma: for. (2) Só com a condição: for total < 100 { … }. (3) O índice e uma cópia do elemento. (4) O range numa string entrega runes com o índice do byte onde começam, e o á ocupa dois bytes. (5) Cada volta do laço agora ganha a sua própria cópia da variável, então closures e goroutines criadas no laço veem o valor da sua própria volta (quando o go.mod diz 1.22 ou mais novo).

Cola rápida

Eu quero…Escreva
Contar de 1 a 10for contagem := 1; contagem <= 10; contagem++ { }
Repetir n vezes (Go 1.22+)for passo := range n { }
Repetir enquanto uma condição valefor total < 100 { }
Repetir até um breakfor { … if pronto { break } … }
Percorrer um slicefor indice, valor := range itens { }
Só valores / só índicesfor _, valor := range itens / for indice := range itens
Percorrer os caracteres de uma stringfor indice, caractere := range texto { }
Percorrer um map (ordem qualquer)for chave, valor := range tabela { }
Pular para a próxima voltacontinue
Sair do laçobreak
Sair de laços aninhadosrotule o laço de fora e use break rotulo
Mudar elementos de um sliceitens[indice] = …, e não a cópia do range

Resumindo

A única palavra-chave for do Go cobre todo tipo de laço:

  • Três partes (início; condição; passo) para contar, sem parênteses e com chaves obrigatórias.
  • Só a condição é o while do Go; sem condição repete até um break.
  • O range percorre slices (índice e uma cópia), strings (runes e índices de bytes), maps (em ordem aleatória) e, desde o Go 1.22, inteiros.
  • O continue pula uma volta, o break sai, e os rótulos saem de laços aninhados.
  • Desde o Go 1.22, cada volta ganha a sua própria variável de laço, consertando o clássico erro das closures (o mesmo código imprimiu 3 3 3 com go 1.21 no go.mod).
  • Laços de índice e com range são igualmente rápidos, cerca de meio nanossegundo por elemento no nosso teste, mas a fórmula certa venceu o laço por milhões de vezes.

Voltando pela série: Como Definir uma Variável em Go, Como Instalar o Go no Linux e Seu Primeiro Programa em Go. A seguir: slices de duas dimensões em Go.

Obrigado pela leitura!

Conhece alguém que vai gostar? Mande para essa pessoa.

Mais artigos

Leia a seguir

Comentários

Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.