O que é Switch em Go?

A instrução switch em Go é uma estrutura de controle de fluxo que avalia uma expressão e executa o bloco correspondente ao primeiro caso (case) que corresponda ao valor. Diferente de C e Java, o switch em Go não precisa de break em cada case — a execução para automaticamente após o case correspondente, eliminando uma classe inteira de bugs causados por break esquecidos.

Go oferece dois tipos de switch: o expression switch (que compara valores) e o type switch (que verifica o tipo dinâmico de uma interface). Ambos são ferramentas poderosas que, combinadas com o if, cobrem praticamente todas as necessidades de controle de fluxo da linguagem.

O switch em Go é surpreendentemente versátil. Ele pode funcionar sem expressão (como uma cadeia de if/else), aceitar múltiplos valores por case, usar expressões complexas e até inicializar variáveis com init statement — assim como o if. Para desenvolvedores vindo de outras linguagens, o switch de Go é ao mesmo tempo familiar e significativamente melhorado.

Expression Switch Básico

A forma mais comum do switch compara uma expressão contra valores constantes:

dia := time.Now().Weekday()

switch dia {
case time.Monday:
    fmt.Println("Segunda-feira — início da semana")
case time.Friday:
    fmt.Println("Sexta-feira — quase fim de semana!")
case time.Saturday, time.Sunday:
    fmt.Println("Fim de semana — descanse!")
default:
    fmt.Println("Meio da semana — continue focado")
}

Observe que Saturday e Sunday compartilham o mesmo case usando vírgula. Isso elimina a necessidade de fallthrough para agrupar valores — um design muito mais seguro que o de C/Java.

Switch com Strings

Switch é especialmente útil para despacho baseado em strings, comum em APIs REST e CLI tools:

func executarComando(cmd string) error {
    switch cmd {
    case "iniciar", "start":
        return iniciarServico()
    case "parar", "stop":
        return pararServico()
    case "reiniciar", "restart":
        return reiniciarServico()
    case "status":
        return mostrarStatus()
    default:
        return fmt.Errorf("comando desconhecido: %s", cmd)
    }
}

Switch sem Expressão (Tagless Switch)

Quando você omite a expressão após switch, cada case pode ter sua própria condição booleana. Isso funciona como uma cadeia de if/else if mais legível:

temperatura := 28

switch {
case temperatura < 0:
    fmt.Println("Congelante")
case temperatura < 15:
    fmt.Println("Frio")
case temperatura < 25:
    fmt.Println("Agradável")
case temperatura < 35:
    fmt.Println("Quente")
default:
    fmt.Println("Muito quente!")
}

O tagless switch (switch { ... } ou equivalente switch true { ... }) é idiomático em Go e frequentemente preferido a longas cadeias de if/else if. Cada case é avaliado na ordem, e o primeiro que for verdadeiro é executado.

Tagless Switch para Validação

func validarUsuario(u *Usuario) error {
    switch {
    case u == nil:
        return errors.New("usuário não pode ser nil")
    case u.Nome == "":
        return errors.New("nome é obrigatório")
    case u.Email == "":
        return errors.New("email é obrigatório")
    case u.Idade < 18:
        return errors.New("usuário deve ser maior de idade")
    case !strings.Contains(u.Email, "@"):
        return errors.New("email inválido")
    }
    return nil
}

Switch com Init Statement

Assim como o if, o switch aceita uma declaração de inicialização antes da expressão:

switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux — ótimo para servidores Go")
case "darwin":
    fmt.Println("macOS — ótimo para desenvolvimento")
case "windows":
    fmt.Println("Windows — suporte completo")
default:
    fmt.Printf("Sistema %s — Go funciona aqui também!\n", os)
}
// os não existe aqui — escopo limitado ao switch

A variável declarada na init statement tem escopo limitado ao bloco switch, mantendo o namespace limpo — o mesmo princípio do init statement do if.

Type Switch

O type switch é uma funcionalidade exclusiva de Go que verifica o tipo dinâmico de uma interface. É essencial para trabalhar com interfaces genéricas e o padrão interface{} (ou any):

func descrever(i interface{}) string {
    switch v := i.(type) {
    case string:
        return fmt.Sprintf("string de %d caracteres: %q", len(v), v)
    case int:
        return fmt.Sprintf("inteiro: %d", v)
    case bool:
        return fmt.Sprintf("booleano: %t", v)
    case []byte:
        return fmt.Sprintf("bytes: %d bytes", len(v))
    case nil:
        return "nil"
    default:
        return fmt.Sprintf("tipo desconhecido: %T", v)
    }
}

func main() {
    fmt.Println(descrever("Olá"))    // string de 3 caracteres: "Olá"
    fmt.Println(descrever(42))       // inteiro: 42
    fmt.Println(descrever(true))     // booleano: true
    fmt.Println(descrever(nil))      // nil
}

A variável v no type switch recebe automaticamente o tipo correspondente ao case, eliminando a necessidade de type assertions manuais. Isso é type-safe — o compilador garante que você só use operações válidas para cada tipo.

Type Switch com Interfaces Personalizadas

Type switches são poderosos quando combinados com interfaces personalizadas, especialmente em sistemas com polimorfismo:

type Forma interface {
    Area() float64
}

type Circulo struct{ Raio float64 }
type Retangulo struct{ Largura, Altura float64 }
type Triangulo struct{ Base, Altura float64 }

func (c Circulo) Area() float64    { return math.Pi * c.Raio * c.Raio }
func (r Retangulo) Area() float64  { return r.Largura * r.Altura }
func (t Triangulo) Area() float64  { return t.Base * t.Altura / 2 }

func descreverForma(f Forma) string {
    switch v := f.(type) {
    case Circulo:
        return fmt.Sprintf("Círculo com raio %.2f (área: %.2f)", v.Raio, v.Area())
    case Retangulo:
        return fmt.Sprintf("Retângulo %0.fx%.0f (área: %.2f)", v.Largura, v.Altura, v.Area())
    case Triangulo:
        return fmt.Sprintf("Triângulo base %.0f (área: %.2f)", v.Base, v.Area())
    default:
        return fmt.Sprintf("Forma com área %.2f", v.Area())
    }
}

Fallthrough

Em Go, cada case é implicitamente terminado — não precisa de break. Se você precisa que a execução continue para o próximo case (raro), use fallthrough:

nivel := 3

switch {
case nivel >= 3:
    fmt.Println("Acesso a admin panel")
    fallthrough
case nivel >= 2:
    fmt.Println("Acesso a relatórios")
    fallthrough
case nivel >= 1:
    fmt.Println("Acesso básico")
}
// Nível 3 imprime todas as três linhas

Atenção: fallthrough em Go é incondicional — ele pula para o próximo case sem avaliar a condição do case seguinte. Por isso, use-o com extrema cautela. Na prática, fallthrough é raramente necessário e geralmente existem alternativas mais claras.

Quando NÃO Usar Fallthrough

Na maioria dos casos, agrupar valores com vírgula é preferível a fallthrough:

// Bom: valores agrupados com vírgula
switch ext {
case ".jpg", ".jpeg", ".png", ".gif", ".webp":
    fmt.Println("Imagem")
case ".mp4", ".avi", ".mkv":
    fmt.Println("Vídeo")
case ".go", ".py", ".js":
    fmt.Println("Código-fonte")
}

Switch em Patterns Comuns de Go

Despacho de HTTP Handlers

func handler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        listarRecursos(w, r)
    case http.MethodPost:
        criarRecurso(w, r)
    case http.MethodPut:
        atualizarRecurso(w, r)
    case http.MethodDelete:
        deletarRecurso(w, r)
    default:
        http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
    }
}

Tratamento de Erros com Type Switch

func tratarErro(err error) {
    switch e := err.(type) {
    case *json.SyntaxError:
        log.Printf("JSON inválido na posição %d", e.Offset)
    case *url.Error:
        log.Printf("Erro de URL: %s — operação: %s", e.URL, e.Op)
    case *net.OpError:
        log.Printf("Erro de rede: %v (timeout: %t)", e.Err, e.Timeout())
    default:
        log.Printf("Erro genérico: %v", err)
    }
}

State Machine com Switch

type Estado int

const (
    Inicial Estado = iota
    Processando
    Concluido
    Erro
)

func transicao(atual Estado, evento string) Estado {
    switch atual {
    case Inicial:
        if evento == "iniciar" {
            return Processando
        }
    case Processando:
        switch evento {
        case "sucesso":
            return Concluido
        case "falha":
            return Erro
        }
    case Erro:
        if evento == "retry" {
            return Processando
        }
    }
    return atual // Estado não muda
}

Boas Práticas com Switch

  1. Sempre inclua default — mesmo que seja para logging ou panic em cases inesperados
  2. Prefira múltiplos valores por case — use vírgula em vez de fallthrough
  3. Use type switch para interfaces — mais seguro que múltiplas type assertions
  4. Considere tagless switch — mais legível que cadeias longas de if/else if
  5. Evite fallthrough — quase sempre existe uma alternativa mais clara
  6. Use context + select para concorrência — switch é para lógica síncrona, select é para channels

Perguntas Frequentes (FAQ)

Por que switch em Go não precisa de break?

Em Go, cada case é implicitamente terminado — a execução para automaticamente ao final do case correspondente. Essa decisão elimina uma classe enorme de bugs em C e Java, onde esquecer o break causa fallthrough acidental. Se você realmente precisa de fallthrough (raro), use a keyword fallthrough explicitamente. Na prática, a maioria dos programas Go nunca usa fallthrough.

O que é type switch em Go?

Type switch é uma forma especial de switch que verifica o tipo dinâmico de uma interface. A sintaxe switch v := i.(type) avalia o tipo concreto armazenado na interface e faz binding da variável v ao tipo correspondente em cada case. É a forma idiomática de trabalhar com interfaces genéricas e any em Go, sendo essencial para serialização, tratamento de erros e polimorfismo.

Quando usar switch vs if em Go?

Use switch quando compara um valor contra três ou mais possibilidades, quando usa type switch para interfaces, ou quando quer uma cadeia de condições mais legível (tagless switch). Use if para condições booleanas simples, verificações de erro com init statement, e guard clauses com early return. A regra prática: se você tem mais de dois else if, migre para switch.

O que é fallthrough em Go e quando usar?

fallthrough em Go força a execução a continuar para o próximo case, sem avaliar a condição desse case. É raro e geralmente desnecessário — na maioria dos cenários, agrupar valores com vírgula (case "a", "b", "c":) ou reestruturar a lógica é preferível. Use fallthrough apenas quando a semântica de cascata é genuinamente necessária, como em sistemas de permissões cumulativas ou parsers de protocolo.