Loops: só o for e suas variações
1. Introdução ao for: a única estrutura de loop do Go
Diferentemente de linguagens como C, Java ou Python que oferecem múltiplas construções de repetição (while, do-while, for), Go adota uma abordagem minimalista: apenas o for. Essa decisão de design reflete a filosofia do Go de simplicidade e clareza, reduzindo a complexidade sintática sem sacrificar expressividade.
O for em Go se manifesta em três variações principais:
- Clássico (estilo C): com inicialização, condição e pós-execução
- Condicional (estilo while): apenas com condição de parada
- Infinito: sem nenhuma condição
Enquanto em Python você teria for e while, e em Java teria for, while e do-while, em Go todas essas necessidades são atendidas por uma única palavra-chave. O do-while, por exemplo, pode ser simulado com um for infinito e um break no final do bloco.
2. O loop for clássico (estilo C)
A forma mais tradicional do for em Go segue a sintaxe herdada de C:
for inicialização; condição; pós-execução {
// corpo do loop
}
As variáveis declaradas na inicialização têm escopo limitado ao bloco do for:
package main
import "fmt"
func main() {
numeros := [5]int{10, 20, 30, 40, 50}
for i := 0; i < len(numeros); i++ {
fmt.Printf("Índice %d: valor %d\n", i, numeros[i])
}
// i não está acessível aqui - causaria erro de compilação
// fmt.Println(i) // erro!
}
3. O loop for condicional (estilo while)
Quando omitimos a inicialização e a pós-execução, o for se comporta exatamente como um while:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
leitor := bufio.NewReader(os.Stdin)
var entrada string
fmt.Print("Digite 'sair' para encerrar: ")
for entrada != "sair" {
texto, _ := leitor.ReadString('\n')
entrada = strings.TrimSpace(texto)
if entrada != "sair" {
fmt.Printf("Você digitou: %s\n", entrada)
fmt.Print("Tente novamente: ")
}
}
fmt.Println("Programa encerrado.")
}
4. O loop for infinito e como controlá-lo
Um for sem nenhuma condição executa indefinidamente até ser interrompido:
package main
import (
"fmt"
"time"
)
func main() {
contador := 0
for {
contador++
fmt.Printf("Iteração %d\n", contador)
if contador%3 == 0 {
fmt.Println("Múltiplo de 3 detectado! Pulando...")
continue // volta ao início do loop
}
if contador >= 10 {
fmt.Println("Limite atingido. Parando.")
break // interrompe o loop
}
time.Sleep(500 * time.Millisecond)
}
}
Um exemplo mais realista seria um servidor simples que aguarda requisições até receber um sinal de parada:
package main
import (
"fmt"
"time"
)
func servidor(parada chan bool) {
for {
select {
case <-parada:
fmt.Println("Servidor encerrando...")
return
default:
fmt.Println("Processando requisição...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
parada := make(chan bool)
go servidor(parada)
time.Sleep(3 * time.Second)
parada <- true
time.Sleep(500 * time.Millisecond)
}
5. Iterando sobre coleções com range
A forma mais idiomática de percorrer coleções em Go é usando range:
package main
import "fmt"
func main() {
// Slices
frutas := []string{"maçã", "banana", "laranja"}
for i, fruta := range frutas {
fmt.Printf("%d: %s\n", i, fruta)
}
// Maps
capitais := map[string]string{
"Brasil": "Brasília",
"Japão": "Tóquio",
"França": "Paris",
}
for pais, capital := range capitais {
fmt.Printf("%s -> %s\n", pais, capital)
}
// Strings (itera sobre runas)
texto := "Olá, Go!"
for posicao, caractere := range texto {
fmt.Printf("Posição %d: %c (U+%04X)\n", posicao, caractere, caractere)
}
}
Para descartar valores não utilizados, use _:
// Somente índices
for i := range frutas {
fmt.Println(i)
}
// Somente valores
for _, fruta := range frutas {
fmt.Println(fruta)
}
Exemplo prático: contando vogais em uma string:
package main
import (
"fmt"
"strings"
)
func contarVogais(texto string) map[rune]int {
vogais := map[rune]int{}
texto = strings.ToLower(texto)
for _, char := range texto {
switch char {
case 'a', 'e', 'i', 'o', 'u':
vogais[char]++
}
}
return vogais
}
func main() {
resultado := contarVogais("Olá, mundo! Como você está?")
fmt.Println("Contagem de vogais:", resultado)
}
6. Controle de fluxo avançado: break e continue com labels
Labels permitem controlar loops aninhados com precisão:
package main
import "fmt"
func main() {
matriz := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
valorProcurado := 5
encontrado := false
externo:
for i := 0; i < len(matriz); i++ {
for j := 0; j < len(matriz[i]); j++ {
if matriz[i][j] == valorProcurado {
fmt.Printf("Valor %d encontrado na posição [%d][%d]\n", valorProcurado, i, j)
encontrado = true
break externo // sai de ambos os loops
}
}
}
if !encontrado {
fmt.Println("Valor não encontrado")
}
}
O continue com label também funciona:
externo:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == j {
continue externo // pula para próxima iteração do loop externo
}
fmt.Printf("(%d, %d) ", i, j)
}
}
// Saída: (0, 1) (0, 2) (1, 0) (1, 2) (2, 0) (2, 1)
7. Boas práticas e armadilhas comuns
Evitar modificar a variável de iteração
// ERRADO: modificar i dentro do loop causa confusão
for i := 0; i < 10; i++ {
if i%2 == 0 {
i += 2 // não faça isso!
}
}
// CORRETO: use continue ou lógica separada
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i)
}
Cuidado com closures capturando variáveis do for range
Este é um dos erros mais famosos em Go:
// ERRADO (pré-Go 1.22)
valores := []int{1, 2, 3, 4, 5}
var funcoes []func()
for _, v := range valores {
funcoes = append(funcoes, func() {
fmt.Println(v) // todas imprimem 5!
})
}
// CORRETO (criando nova variável a cada iteração)
for _, v := range valores {
v := v // cria uma cópia local
funcoes = append(funcoes, func() {
fmt.Println(v)
})
}
// A partir do Go 1.22, o comportamento foi corrigido
// e a variável de iteração é recriada a cada iteração
Preferir range sobre índices quando possível
// Menos idiomático
for i := 0; i < len(frutas); i++ {
fmt.Println(frutas[i])
}
// Mais idiomático e seguro
for _, fruta := range frutas {
fmt.Println(fruta)
}
Exemplo: erro clássico com goroutines
// ERRADO (pré-Go 1.22)
for _, tarefa := range tarefas {
go func() {
processar(tarefa) // todas as goroutines veem o mesmo valor
}()
}
// CORRETO
for _, tarefa := range tarefas {
tarefa := tarefa
go func() {
processar(tarefa)
}()
}
O loop for em Go, apesar de sua simplicidade, oferece toda a flexibilidade necessária para qualquer padrão de repetição. Dominar suas variações e entender as armadilhas comuns é essencial para escrever código Go idiomático e correto.
Referências
- Effective Go - For statements — Documentação oficial com exemplos e boas práticas sobre loops em Go
- Go by Example: For — Tutoriais práticos demonstrando todas as variações do loop for
- The Go Blog - Loops in Go — Artigo oficial do time Go sobre a filosofia por trás da estrutura de loops
- Go Specification - For statements — Especificação técnica completa da linguagem sobre a sintaxe do for
- A Tour of Go - For — Tutorial interativo oficial cobrindo o básico do loop for em Go