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