Type switches: inspecionando tipos em runtime
1. O que são Type Switches e por que usá-los?
Type switches são uma construção especial da linguagem Go que permite inspecionar o tipo dinâmico de uma interface em tempo de execução. Diferentemente de um switch comum, que compara valores, um type switch compara tipos.
A sintaxe básica é:
switch v := x.(type) {
case int:
fmt.Printf("É um inteiro: %d\n", v)
case string:
fmt.Printf("É uma string: %s\n", v)
default:
fmt.Printf("Tipo desconhecido: %T\n", v)
}
A diferença fundamental entre um type switch e um switch comum é que no type switch a expressão x.(type) só pode ser usada dentro da estrutura do switch, e cada case verifica se o valor da interface corresponde a um tipo específico.
Type switches são especialmente úteis quando trabalhamos com interface{} (ou any em Go 1.18+) e precisamos tratar valores de tipos diferentes de forma específica. Eles são a ferramenta ideal para cenários onde o tipo concreto do valor só é conhecido em runtime.
2. Sintaxe e casos de uso fundamentais
A estrutura completa de um type switch inclui a declaração de uma variável que recebe o valor com o tipo já convertido:
func processValue(x any) {
switch v := x.(type) {
case int:
fmt.Println("Inteiro:", v*2) // v é int
case string:
fmt.Println("String:", len(v)) // v é string
case bool:
fmt.Println("Booleano:", v) // v é bool
default:
fmt.Printf("Tipo inesperado: %T\n", v)
}
}
A variável v tem escopo dentro do bloco case e já vem com o tipo correspondente, eliminando a necessidade de type assertions manuais. O caso default é importante para capturar tipos não previstos e evitar comportamentos silenciosos.
3. Combinando Type Switch com Type Assertions
Type assertions (x.(T)) e type switches são conceitos relacionados, mas o type switch simplifica dramaticamente o código quando múltiplos tipos precisam ser verificados.
Compare estas abordagens:
// Abordagem com type assertions aninhadas (ruim)
func processWithAssertions(x any) {
if v, ok := x.(int); ok {
fmt.Println("Inteiro:", v)
} else if v, ok := x.(string); ok {
fmt.Println("String:", v)
} else if v, ok := x.(bool); ok {
fmt.Println("Bool:", v)
} else {
fmt.Println("Tipo desconhecido")
}
}
// Abordagem com type switch (elegante)
func processWithSwitch(x any) {
switch v := x.(type) {
case int:
fmt.Println("Inteiro:", v)
case string:
fmt.Println("String:", v)
case bool:
fmt.Println("Bool:", v)
default:
fmt.Println("Tipo desconhecido")
}
}
O type switch elimina a repetição de código e torna a intenção mais clara, além de ser mais performático que múltiplas type assertions encadeadas.
4. Múltiplos tipos em um único case
Go permite agrupar múltiplos tipos em um único case usando vírgula:
func handleNumeric(v any) {
switch val := v.(type) {
case int, int32, int64:
fmt.Println("Inteiro (qualquer tamanho):", val)
case float32, float64:
fmt.Println("Float:", val)
case string:
fmt.Println("String:", val)
default:
fmt.Printf("Tipo: %T\n", val)
}
}
Cuidado importante: quando múltiplos tipos são agrupados, a variável val mantém o tipo da interface original (não é convertida para um tipo específico). Você precisará fazer uma type assertion manual dentro do bloco se precisar do valor com tipo concreto.
case int, int32, int64:
// val ainda é any aqui
switch concrete := val.(type) {
case int:
fmt.Println("int:", concrete)
case int32:
fmt.Println("int32:", concrete)
}
5. Type Switch com interfaces customizadas
Type switches também podem verificar se um valor implementa uma interface específica:
type Stringer interface {
String() string
}
func inspectValue(x any) {
switch v := x.(type) {
case int:
fmt.Printf("Número: %d\n", v)
case string:
fmt.Printf("Texto: %s\n", v)
case Stringer:
// Verifica se implementa Stringer
fmt.Printf("Implementa Stringer: %s\n", v.String())
case io.Reader:
// Verifica se implementa io.Reader
data, _ := io.ReadAll(v)
fmt.Printf("Reader: %s\n", data)
default:
fmt.Printf("Tipo: %T\n", v)
}
}
A ordem dos case importa: Go testa na sequência em que foram escritos. Coloque tipos mais específicos antes de interfaces mais genéricas.
6. Padrões avançados e boas práticas
Type switches aninhados: embora possível, evite aninhar type switches profundamente. Se necessário, extraia a lógica para funções separadas:
func processNested(x any) {
switch v := x.(type) {
case map[string]any:
for key, val := range v {
processNested(val) // recursão para valores do mapa
}
case []any:
for _, item := range v {
processNested(item)
}
default:
fmt.Println("Valor final:", v)
}
}
Performance: type switches são otimizados pelo compilador Go e geralmente são mais rápidos que usar o pacote reflect. Para a maioria dos casos, type switches são a melhor opção. Use reflect apenas quando precisar de introspecção mais avançada (como ler tags de struct ou criar tipos dinamicamente).
// Prefira type switch
switch v := x.(type) {
case int:
// rápido
}
// Evite reflect quando possível
import "reflect"
t := reflect.TypeOf(x) // mais lento
7. Exemplos reais e casos comuns no ecossistema Go
Serialização/deserialização com JSON:
func processJSON(data []byte) {
var result any
json.Unmarshal(data, &result)
switch v := result.(type) {
case map[string]any:
for key, val := range v {
fmt.Printf("Chave: %s, Valor: %v\n", key, val)
}
case []any:
for i, item := range v {
fmt.Printf("Índice %d: %v\n", i, item)
}
default:
fmt.Printf("Valor simples: %v\n", v)
}
}
Handlers HTTP com tipos variados:
func apiHandler(w http.ResponseWriter, r *http.Request) {
var payload any
json.NewDecoder(r.Body).Decode(&payload)
switch data := payload.(type) {
case map[string]any:
handleMapPayload(w, data)
case []any:
handleSlicePayload(w, data)
case string:
handleStringPayload(w, data)
default:
http.Error(w, "payload inválido", http.StatusBadRequest)
}
}
Sistema de eventos:
type Event interface{}
type UserCreated struct {
ID int
Name string
}
type OrderPlaced struct {
OrderID int
Amount float64
}
func handleEvent(event Event) {
switch e := event.(type) {
case UserCreated:
fmt.Printf("Usuário criado: %s (ID: %d)\n", e.Name, e.ID)
case OrderPlaced:
fmt.Printf("Pedido realizado: #%d (R$ %.2f)\n", e.OrderID, e.Amount)
default:
fmt.Printf("Evento desconhecido: %T\n", e)
}
}
8. Armadilhas e erros frequentes
Esquecer o default: sempre inclua um caso default para tratar tipos inesperados, especialmente em código de produção.
Confundir as sintaxes: switch v := x.(type) (com variável) vs switch x.(type) (sem variável):
// Sem variável - não é possível usar o valor convertido
switch x.(type) {
case int:
// Aqui não temos acesso ao valor como int
fmt.Println("É int, mas não posso usar o valor")
}
// Com variável - podemos usar o valor com tipo correto
switch v := x.(type) {
case int:
fmt.Println(v * 2) // v é int
}
Type switch com ponteiros vs. valores: Um *int não é a mesma coisa que int:
func checkPointer(x any) {
switch v := x.(type) {
case *int:
fmt.Println("Ponteiro para int:", *v)
case int:
fmt.Println("Inteiro:", v)
case nil:
fmt.Println("É nil!")
default:
fmt.Printf("Tipo: %T\n", v)
}
}
Cuidado com nil: Uma interface com valor nil pode se comportar de forma inesperada. Sempre trate o caso nil explicitamente se necessário.
var x any = (*int)(nil) // interface com valor nil, mas tipo *int
switch v := x.(type) {
case *int:
fmt.Println(v == nil) // true, mas entrou aqui, não no case nil
case nil:
fmt.Println("Nunca chega aqui")
}
Type switches são uma das ferramentas mais elegantes do Go para lidar com polimorfismo e tipos dinâmicos. Dominá-los é essencial para escrever código Go idiomático e eficiente.
Referências
- A Tour of Go: Type switches — Tutorial interativo oficial sobre type switches na documentação do Go
- Effective Go: Type switches — Guia oficial com boas práticas e exemplos de uso de type switches
- Go by Example: Type Switch — Exemplo prático e direto de type switch com código executável
- The Go Blog: JSON and Go — Artigo oficial sobre JSON em Go, com exemplos de uso de type switches para tratar dados dinâmicos
- Go 101: Type Switch — Explicação detalhada sobre type switches, incluindo nuances e casos avançados
- Stack Overflow: Type switch vs type assertion — Discussão técnica sobre diferenças entre type switch e type assertion