O controle de fluxo é a espinha dorsal de qualquer programa. Em Go, a sintaxe é limpa e direta, mas cheia de nuances que a tornam poderosa e expressiva. Nesta aula, vamos mergulhar nas estruturas condicionais e de repetição que o Go oferece, entendendo não apenas a sintaxe, mas também as boas práticas e casos de uso que farão seu código mais idiomático.

Vamos explorar o if com inicialização, o for como única estrutura de loop (flexível o suficiente para cobrir todos os padrões), o switch com sua quebra automática de casos e os labels que permitem controle de fluxo refinado em loops aninhados. Ao final, você estará apto a escrever lógica de controle robusta e elegante em Go.

if (com init statement)

Em Go, a instrução if é usada para executar um bloco de código se uma condição for verdadeira. Diferente de muitas linguagens, a condição não precisa estar entre parênteses, o que torna o código mais limpo. Mas o recurso mais distintivo é a possibilidade de incluir uma instrução de inicialização antes da condição, separada por ponto e vírgula. Essa instrução é executada antes da avaliação da condição e tem escopo limitado ao bloco do if (incluindo o else).

Essa construção é muito útil para executar uma função que retorna valores e, em seguida, testar um deles, sem poluir o escopo externo. Por exemplo, ao tentar abrir um arquivo, podemos verificar o erro diretamente. Vejamos um exemplo:

package main

import (
    "fmt"
    "os"
)

func main() {
    // A função os.ReadFile retorna ([]byte, error)
    // O init statement atribui os valores e a condição testa o erro
    if data, err := os.ReadFile("exemplo.txt"); err != nil {
        fmt.Println("Erro ao ler arquivo:", err)
    } else {
        fmt.Println("Conteúdo:", string(data))
    }
    // Aqui, data e err não estão mais acessíveis (escopo do if)
}

Esse padrão é extremamente comum em Go, especialmente ao lidar com operações que retornam múltiplos valores, como leituras, escritas e chamadas de rede. Ele evita declarações temporárias e mantém o código conciso e seguro, pois as variáveis não vazam para fora do bloco.

for (a única forma de loop)

Go tem apenas uma instrução de repetição: o for. No entanto, ela é flexível e pode ser usada de várias maneiras: como um loop clássico com inicialização, condição e pós-incremento; como um loop estilo while; e até como um loop infinito. Além disso, a combinação com range permite iterar sobre slices, maps, strings e canais.

Vejamos as variações principais:

package main

import "fmt"

func main() {
    // 1. Loop clássico
    for i := 0; i < 5; i++ {
        fmt.Println("i =", i)
    }

    // 2. Loop estilo while
    j := 0
    for j < 5 {
        fmt.Println("j =", j)
        j++
    }

    // 3. Loop infinito (com break)
    k := 0
    for {
        fmt.Println("k =", k)
        k++
        if k == 3 {
            break
        }
    }

    // 4. Iterando sobre uma slice com range
    frutas := []string{"maçã", "banana", "laranja"}
    for indice, fruta := range frutas {
        fmt.Printf("%d: %s\n", indice, fruta)
    }

    // 5. Iterando sobre um map
    idades := map[string]int{"Alice": 30, "Bob": 25}
    for nome, idade := range idades {
        fmt.Printf("%s tem %d anos\n", nome, idade)
    }
}

No loop clássico, a inicialização e o pós-incremento são opcionais. Quando omitimos a condição, temos um loop infinito, que pode ser interrompido com break. O range é fundamental para trabalhar com coleções e retorna índices/valores (ou chaves/valores para maps). Se não precisar do índice, use _ para descartá-lo. Isso evita variáveis não utilizadas, que são um erro de compilação em Go.

switch

O switch em Go é mais poderoso do que na maioria das linguagens. Ele não exige break explícito ao final de cada caso, pois o Go automaticamente sai do switch após executar o bloco do caso correspondente. Além disso, a expressão avaliada pode ser de qualquer tipo comparável, e os casos podem conter múltiplas expressões separadas por vírgula. Também é possível usar uma forma sem expressão, que funciona como uma cadeia de if-else.

Outra característica interessante é a palavra-chave fallthrough, que força a execução do próximo caso, mesmo sem que a condição seja verdadeira. Isso deve ser usado com parcimônia, pois pode tornar o fluxo confuso. Vejamos exemplos:

package main

import "fmt"

func main() {
    // Switch simples com valor
    dia := "sábado"
    switch dia {
    case "segunda", "terça", "quarta", "quinta", "sexta":
        fmt.Println("Dia útil")
    case "sábado", "domingo":
        fmt.Println("Fim de semana")
    default:
        fmt.Println("Dia inválido")
    }

    // Switch sem expressão (equivale a if-else)
    idade := 18
    switch {
    case idade < 18:
        fmt.Println("Menor de idade")
    case idade <= 60:
        fmt.Println("Adulto")
    default:
        fmt.Println("Idoso")
    }

    // Uso de fallthrough
    num := 2
    switch num {
    case 1:
        fmt.Println("Um")
        fallthrough
    case 2:
        fmt.Println("Dois")
        fallthrough
    case 3:
        fmt.Println("Três")
    default:
        fmt.Println("Outro")
    }
}

No exemplo com fallthrough, quando num é 2, ele imprime "Dois" e depois "Três", pois o fallthrough força a execução do próximo caso. Note que o default não é executado a menos que seja alcançado por fallthrough ou que nenhum caso corresponda.

Labels

Labels em Go são identificadores que podem ser usados para rotular instruções, principalmente para controlar loops a partir de um bloco interno. Eles são úteis quando precisamos interromper ou continuar um loop externo a partir de um loop aninhado. Sem labels, teríamos que usar variáveis de controle ou flags, o que polui o código. Com labels, podemos fazer break ou continue diretamente para o loop desejado.

Um label deve ser seguido por uma instrução (geralmente um loop) e é declarado antes dela. A sintaxe é: nomeDoLabel:. Vejamos um exemplo prático de busca em uma matriz:

package main

import "fmt"

func main() {
    matriz := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

    alvo := 5
    encontrado := false

    // Label para o loop externo
    externo:
    for i, linha := range matriz {
        for j, valor := range linha {
            if valor == alvo {
                fmt.Printf("Encontrado %d na posição [%d][%d]\n", alvo, i, j)
                encontrado = true
                break externo // Interrompe o loop externo
            }
        }
    }

    if !encontrado {
        fmt.Println("Não encontrado")
    }

    // Exemplo de continue com label: imprimir apenas números ímpares
    fmt.Println("Números ímpares:")
    externo2:
    for i := 1; i <= 5; i++ {
        for j := 1; j <= 5; j++ {
            if (i*j)%2 == 0 {
                continue externo2 // Pula para a próxima iteração do loop externo
            }
            fmt.Println(i, "*", j, "=", i*j)
        }
    }
}

No primeiro exemplo, o break externo interrompe o loop externo assim que o valor é encontrado. No segundo, o continue externo2 faz com que o loop externo avance para a próxima iteração, ignorando as iterações internas restantes. Isso é muito útil para otimizar buscas e processamentos em estruturas aninhadas.

Boas Práticas e Observações Finais

Ao usar if com init, lembre-se de que as variáveis declaradas ali só existem dentro do bloco. Prefira essa forma quando a variável for usada somente na condição e no corpo, evitando poluir o escopo externo. No for, evite criar loops infinitos sem uma condição de saída clara; prefira usar uma condição explícita sempre que possível. No switch, não se esqueça de que o Go não exige break; isso evita erros comuns de outras linguagens. Use fallthrough somente quando a intenção for clara e documentada. Labels devem ser usados com moderação; se você precisar de muitos, considere refatorar o código para funções menores, pois loops aninhados complexos podem se tornar difíceis de ler.

Exercícios

  1. Escreva um programa que use um for para imprimir os números de 1 a 10, mas que pare (break) se encontrar um número divisível por 7.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        for i := 1; i <= 10; i++ {
            if i%7 == 0 {
                break
            }
            fmt.Println(i)
        }
    }
  2. Utilizando um if com init statement, escreva uma função que receba um inteiro n e retorne o quadrado de n se n for par, ou o cubo se for ímpar. Faça a chamada no main e imprima o resultado.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func calcular(n int) int {
        if quad := n * n; n%2 == 0 {
            return quad
        } else {
            return n * n * n
        }
    }
    
    func main() {
        fmt.Println(calcular(4)) // 16
        fmt.Println(calcular(3)) // 27
    }
  3. Crie um loop infinito que imprima números crescentes a partir de 0, mas que pare quando o número for maior que 100 e divisível por 10. Use break.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        i := 0
        for {
            if i > 100 && i%10 == 0 {
                break
            }
            fmt.Println(i)
            i++
        }
    }
  4. Escreva um programa que use switch para classificar uma nota (0-10) em conceitos: A (9-10), B (7-8), C (5-6), D (3-4), F (0-2). Use a forma sem expressão.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func conceito(nota int) string {
        switch {
        case nota >= 9:
            return "A"
        case nota >= 7:
            return "B"
        case nota >= 5:
            return "C"
        case nota >= 3:
            return "D"
        default:
            return "F"
        }
    }
    
    func main() {
        fmt.Println(conceito(8)) // B
    }
  5. Dada a matriz abaixo, use um label para encontrar a primeira ocorrência do número 0 e imprimir a posição. Se não houver, imprima "não encontrado".

    matriz := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        matriz := [][]int{
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9},
        }
    
        alvo := 0
        encontrado := false
    
        externo:
        for i, linha := range matriz {
            for j, valor := range linha {
                if valor == alvo {
                    fmt.Printf("Encontrado %d na posição [%d][%d]\n", alvo, i, j)
                    encontrado = true
                    break externo
                }
            }
        }
        if !encontrado {
            fmt.Println("não encontrado")
        }
    }

Referências