Interface vazia: any e type assertions

1. O que é a interface vazia (interface{} / any)

Em Go, uma interface vazia é uma interface que não declara nenhum método. Isso significa que todos os tipos da linguagem implementam essa interface implicitamente. Antes do Go 1.18, escrevíamos interface{}; a partir dessa versão, o alias any foi introduzido como uma forma mais legível e idiomática.

var x any       // equivalente a var x interface{}
x = 42
x = "hello"
x = 3.14
x = struct{ Name string }{"Alice"}

O código acima compila sem erros porque any aceita qualquer valor. Essa característica torna a interface vazia extremamente flexível, mas também exige cuidado: o compilador não pode mais verificar os tipos em tempo de compilação.

2. Por que usar a interface vazia?

A interface vazia é útil em cenários que exigem máxima flexibilidade:

  • APIs genéricas: fmt.Println aceita any para imprimir qualquer valor.
  • Serialização: json.Marshal e json.Unmarshal trabalham com any.
  • Containers heterogêneos: slices e maps que armazenam tipos mistos.
func printAny(v any) {
    fmt.Printf("Valor: %v, Tipo: %T\n", v, v)
}

func main() {
    printAny(100)
    printAny("texto")
    printAny([]int{1, 2, 3})

    // Slice heterogêneo
    misturado := []any{42, "golang", 3.14, true}
    fmt.Println(misturado)
}

Limitação importante: ao usar any, perdemos a segurança de tipo em tempo de compilação. Erros só aparecerão em execução, o que pode levar a panics inesperados.

3. Type assertions: extraindo o tipo concreto

Type assertion é o mecanismo para recuperar o tipo concreto de um valor armazenado em uma interface vazia.

Sintaxe básica (propensa a panic)

var v any = "Go é fantástico"
s := v.(string)      // funciona
n := v.(int)         // panic: interface conversion: interface {} is string, not int

Forma segura com dois retornos

var v any = "Go é fantástico"

if s, ok := v.(string); ok {
    fmt.Println("É uma string:", s)
} else {
    fmt.Println("Não é uma string")
}

if n, ok := v.(int); ok {
    fmt.Println("É um int:", n)
} else {
    fmt.Println("Não é um int")
}

Sempre prefira a forma segura com ok em código de produção.

Exemplo prático: função que processa um any

func processValue(v any) {
    switch {
    case v == nil:
        fmt.Println("valor nulo")
    default:
        if s, ok := v.(string); ok {
            fmt.Println("String em maiúsculas:", strings.ToUpper(s))
        } else if i, ok := v.(int); ok {
            fmt.Println("Inteiro ao quadrado:", i*i)
        } else {
            fmt.Printf("Tipo desconhecido: %T\n", v)
        }
    }
}

4. Type switches: inspecionando múltiplos tipos

Type switch é uma construção mais elegante para testar múltiplos tipos possíveis:

func describe(v any) {
    switch val := v.(type) {
    case nil:
        fmt.Println("nil")
    case string:
        fmt.Printf("string de tamanho %d: %s\n", len(val), val)
    case int:
        fmt.Printf("int: %d\n", val)
    case float64:
        fmt.Printf("float64: %f\n", val)
    case bool:
        fmt.Printf("bool: %v\n", val)
    default:
        fmt.Printf("tipo inesperado: %T\n", val)
    }
}

func main() {
    describe(42)
    describe("Olá")
    describe(3.14)
    describe(true)
    describe(nil)
    describe([]byte{1, 2, 3})
}

A variável val dentro de cada case já possui o tipo correspondente, eliminando a necessidade de type assertions adicionais.

5. Cuidados e boas práticas com type assertions

Sempre usar a forma segura

// ❌ Propenso a panic
func extrairString(v any) string {
    return v.(string)
}

// ✅ Seguro
func extrairStringSeguro(v any) (string, bool) {
    s, ok := v.(string)
    return s, ok
}

Evitar type assertions excessivas

Se você precisa fazer múltiplas type assertions em sequência, considere usar type switch ou, melhor ainda, redefinir sua API com interfaces mais específicas.

// ❌ Ruim: múltiplas assertions
func process(v any) {
    if s, ok := v.(string); ok { /* ... */ }
    if i, ok := v.(int); ok { /* ... */ }
    if f, ok := v.(float64); ok { /* ... */ }
}

// ✅ Melhor: type switch
func process(v any) {
    switch v.(type) {
    case string:  /* ... */
    case int:     /* ... */
    case float64: /* ... */
    }
}

// ✅ Ideal: interface específica
type Stringer interface {
    String() string
}

6. Relação com outros temas da série

Receivers por valor vs. por ponteiro

any pode conter tanto valores quanto ponteiros. Isso afeta o comportamento de type assertions:

var v any = &Person{Name: "Alice"}
p, ok := v.(*Person) // ok, assertion em ponteiro

Contraste com interfaces tipadas

type Animal interface {
    Som() string
}

// any não oferece contrato comportamental
var a any = "cachorro"
// a.Som() // erro de compilação: any não tem método Som

// Interface tipada oferece garantias
var animal Animal = Cachorro{}
animal.Som() // funciona

Erros em Go

error é uma interface específica com um único método (Error() string), não um any:

var err error = fmt.Errorf("algo deu errado")
var v any = err
e, ok := v.(error) // true, pois error implementa a interface

7. Casos de uso reais e alternativas modernas

Uso em bibliotecas padrão

// encoding/json
var data any
json.Unmarshal([]byte(`{"nome":"Alice","idade":30}`), &data)
m := data.(map[string]any)
fmt.Println(m["nome"]) // Alice

// database/sql
var result any
db.QueryRow("SELECT name FROM users WHERE id=?", 1).Scan(&result)

Alternativa moderna: generics (Go 1.18+)

Com generics, muitos usos de any podem ser substituídos por código mais seguro:

// Antes: any
func SomaInteiros(vals []any) int {
    total := 0
    for _, v := range vals {
        if i, ok := v.(int); ok {
            total += i
        }
    }
    return total
}

// Depois: generics
func Soma[T int | float64](vals []T) T {
    var total T
    for _, v := range vals {
        total += v
    }
    return total
}

Quando ainda faz sentido usar any

  • APIs que realmente precisam aceitar qualquer tipo (ex: fmt.Println, json.Marshal)
  • Interfaces com sistemas externos onde o tipo só é conhecido em tempo de execução
  • Código legado que ainda não foi migrado para generics

Em projetos novos, prefira generics sempre que possível, mas mantenha any como ferramenta para casos legítimos de heterogeneidade dinâmica.

Referências