Switch em Go: sem fallthrough por padrão

1. A sintaxe básica do switch em Go

Em Go, a estrutura switch é uma das ferramentas mais elegantes para controle de fluxo condicional. Diferente de linguagens como C, C++ ou Java, o switch em Go não exige a declaração explícita de break após cada case. A execução não "cai" automaticamente para o próximo caso — a menos que você explicitamente queira isso.

package main

import "fmt"

func main() {
    dia := 3
    switch dia {
    case 1:
        fmt.Println("Segunda-feira")
    case 2:
        fmt.Println("Terça-feira")
    case 3:
        fmt.Println("Quarta-feira")
    case 4:
        fmt.Println("Quinta-feira")
    case 5:
        fmt.Println("Sexta-feira")
    default:
        fmt.Println("Fim de semana")
    }
    // Saída: Quarta-feira
}

Note que não há break entre os case. Em C, o mesmo código sem break imprimiria "Quarta-feira", "Quinta-feira", "Sexta-feira" e "Fim de semana" — um comportamento que frequentemente causa bugs. Go elimina esse problema por padrão.

2. Comportamento padrão: sem fallthrough

A decisão de design de não propagar execução entre case foi intencional. As vantagens são claras:

  • Menos bugs: O erro clássico de esquecer um break em C/C++ simplesmente não existe em Go.
  • Código mais legível: Cada case é independente e auto-contido.
  • Refatoração segura: Reordenar ou adicionar case não quebra o fluxo.
func classificarNota(nota int) string {
    switch {
    case nota >= 90:
        return "A"
    case nota >= 80:
        return "B"
    case nota >= 70:
        return "C"
    case nota >= 60:
        return "D"
    default:
        return "F"
    }
}

Aqui, cada case é mutuamente exclusivo. Se a nota for 95, apenas o primeiro case executa — mesmo que os outros também sejam verdadeiros.

3. Fallthrough explícito com a palavra-chave fallthrough

Apesar do comportamento padrão ser sem propagação, Go oferece a palavra-chave fallthrough para forçar a execução do próximo case. Seu uso é explícito e intencional.

func main() {
    i := 2
    switch i {
    case 1:
        fmt.Println("Um")
        fallthrough
    case 2:
        fmt.Println("Dois")
        fallthrough
    case 3:
        fmt.Println("Três")
    default:
        fmt.Println("Outro")
    }
    // Saída:
    // Dois
    // Três
}

Limitações importantes do fallthrough:
- Só pode pular para o próximo case imediato, não para um case arbitrário.
- Não funciona no último case ou no default.
- A expressão do próximo case não é reavaliada — o fallthrough simplesmente executa o corpo do próximo caso.

Boas práticas: Use fallthrough raramente. Na maioria dos cenários, múltiplos valores em um único case são mais claros:

switch letra {
case 'a', 'e', 'i', 'o', 'u':
    fmt.Println("Vogal")
default:
    fmt.Println("Consoante")
}

4. Switch com inicialização inline (statement opcional)

Go permite incluir uma declaração de inicialização antes da expressão do switch, similar ao for:

func main() {
    switch x := obterValor(); x {
    case 10:
        fmt.Println("Dez")
    case 20:
        fmt.Println("Vinte")
    default:
        fmt.Println("Outro valor:", x)
    }
    // x não existe aqui fora
}

A variável x tem escopo limitado ao bloco do switch. Isso é útil para:
- Manter o escopo restrito
- Evitar poluir o namespace da função
- Realizar cálculos ou chamadas de função uma única vez

switch n := rand.Intn(100); {
case n < 50:
    fmt.Println("Menor que 50")
case n == 50:
    fmt.Println("Exatamente 50")
default:
    fmt.Println("Maior que 50")
}

5. Switch sem expressão (switch true)

Um padrão extremamente comum em Go é o switch sem expressão, que equivale a switch true. Cada case contém uma expressão booleana:

func main() {
    idade := 25
    switch {
    case idade < 13:
        fmt.Println("Criança")
    case idade < 18:
        fmt.Println("Adolescente")
    case idade < 60:
        fmt.Println("Adulto")
    default:
        fmt.Println("Idoso")
    }
}

Este padrão substitui elegantemente longas cadeias de if-else if, tornando o código mais limpo e legível. Cada case é avaliado em ordem até encontrar o primeiro verdadeiro.

6. Switch com tipos (type switch)

Go possui um recurso poderoso para trabalhar com interfaces: o type switch. Ele permite verificar o tipo concreto de uma interface em tempo de execução:

func identificarTipo(v interface{}) string {
    switch v.(type) {
    case int:
        return "Inteiro"
    case string:
        return "String"
    case bool:
        return "Booleano"
    case []int:
        return "Slice de inteiros"
    default:
        return "Tipo desconhecido"
    }
}

func main() {
    fmt.Println(identificarTipo(42))          // Inteiro
    fmt.Println(identificarTipo("Go"))        // String
    fmt.Println(identificarTipo([]int{1,2,3})) // Slice de inteiros
}

Para acessar o valor tipado, use a sintaxe com variável:

switch v := valor.(type) {
case int:
    fmt.Println("Inteiro:", v*2)
case string:
    fmt.Println("String:", len(v))
}

7. Comparação com arrays, loops e controle de fluxo vizinhos

O switch em Go se alinha perfeitamente com a filosofia minimalista da linguagem. Enquanto Go tem apenas for para loops (sem while ou do-while), o switch substitui múltiplas construções condicionais.

Switch vs if-else:

// Com if-else
if x == 1 {
    // ...
} else if x == 2 {
    // ...
} else if x == 3 {
    // ...
} else {
    // ...
}

// Com switch - mais limpo
switch x {
case 1:
    // ...
case 2:
    // ...
case 3:
    // ...
default:
    // ...
}

Switch combinado com range e arrays:

notas := []int{85, 92, 67, 73, 88}
for _, nota := range notas {
    switch {
    case nota >= 90:
        fmt.Printf("%d: A\n", nota)
    case nota >= 80:
        fmt.Printf("%d: B\n", nota)
    default:
        fmt.Printf("%d: C ou inferior\n", nota)
    }
}

8. Erros comuns e boas práticas

Erro 1: Fallthrough acidental ou excessivo

// RUIM: fallthrough desnecessário
switch comando {
case "sair":
    fmt.Println("Saindo...")
    fallthrough // Por que? Isso não faz sentido!
case "help":
    mostrarAjuda()
}

Erro 2: Esquecer que default não precisa ser o último

switch status {
case "ativo":
    // ...
default:
    fmt.Println("Status desconhecido")
case "inativo":
    // ...
}
// Isso funciona, mas é confuso. Coloque default sempre no final.

Boas práticas:

  1. Cases alinhados: Mantenha os case alinhados verticalmente para legibilidade.
  2. Default sempre: Inclua um caso default para tratar valores inesperados.
  3. Preferir múltiplos valores: Em vez de fallthrough, use case 1, 2, 3:.
  4. Switch sem expressão para condições complexas: Use switch { ... } em vez de if-else if quando houver 3 ou mais condições.
  5. Type switch com cuidado: Lembre-se que type switch só funciona com interfaces.
// Boa prática: múltiplos valores em um case
switch dia {
case "sábado", "domingo":
    fmt.Println("Fim de semana")
default:
    fmt.Println("Dia útil")
}

O switch em Go é uma ferramenta versátil que, quando usada corretamente, produz código limpo, seguro e expressivo. Sua simplicidade — sem fallthrough automático — é uma das muitas decisões de design que tornam Go uma linguagem pragmática e agradável de usar.


Referências