Reflection: inspecionando tipos em runtime

1. Introdução à Reflection em Go

Reflection é a capacidade de um programa examinar sua própria estrutura em tempo de execução. Em Go, isso é implementado através do pacote reflect, que permite inspecionar tipos, acessar campos de structs, modificar valores e até chamar funções dinamicamente.

A reflection existe em Go principalmente para preencher lacunas onde o sistema de tipos estático é insuficiente. Casos de uso comuns incluem:

  • Serialização/deserialização: pacotes como encoding/json e encoding/xml usam reflection para mapear structs para formatos de dados
  • Testes: frameworks de teste podem inspecionar structs para gerar dados de teste automaticamente
  • Injeção de dependência: containers que resolvem dependências em tempo de execução
  • ORM: mapeamento objeto-relacional que converte structs em queries SQL

Os dois tipos centrais do pacote reflect são:

  • reflect.Type: representa o tipo de uma variável Go
  • reflect.Value: representa o valor real armazenado em uma variável

2. Inspecionando Tipos com reflect.Type

A função reflect.TypeOf() aceita qualquer interface e retorna o tipo dinâmico do valor armazenado:

package main

import (
    "fmt"
    "reflect"
)

type Usuario struct {
    Nome  string
    Idade int
}

func main() {
    var x float64 = 3.14
    t := reflect.TypeOf(x)
    fmt.Println("Tipo:", t)           // float64
    fmt.Println("Kind:", t.Kind())    // float64
    fmt.Println("Name:", t.Name())    // float64

    u := Usuario{Nome: "Alice", Idade: 30}
    tu := reflect.TypeOf(u)
    fmt.Println("Tipo:", tu)          // main.Usuario
    fmt.Println("Kind:", tu.Kind())   // struct
    fmt.Println("Name:", tu.Name())   // Usuario
}

Para navegar por tipos complexos, usamos métodos como Elem() (para obter o tipo subjacente de ponteiros, slices, arrays) e Field() (para structs):

func inspecionar(v interface{}) {
    t := reflect.TypeOf(v)
    fmt.Printf("Tipo: %v, Kind: %v\n", t, t.Kind())

    // Se for ponteiro, obtém o tipo apontado
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        fmt.Printf("Tipo apontado: %v, Kind: %v\n", t, t.Kind())
    }

    // Se for struct, lista os campos
    if t.Kind() == reflect.Struct {
        for i := 0; i < t.NumField(); i++ {
            field := t.Field(i)
            fmt.Printf("  Campo %d: %s (%s)\n", i, field.Name, field.Type)
        }
    }
}

func main() {
    inspecionar(&Usuario{Nome: "Bob", Idade: 25})
}

3. Trabalhando com reflect.Value

reflect.ValueOf() retorna o valor real contido em uma interface. Para modificar valores, precisamos de um ponteiro e verificar se o valor é settable:

func modificarValor(v interface{}) {
    pv := reflect.ValueOf(v)

    // Verifica se é ponteiro e se pode ser modificado
    if pv.Kind() != reflect.Ptr || !pv.Elem().CanSet() {
        fmt.Println("Não é possível modificar")
        return
    }

    // Obtém o valor apontado
    val := pv.Elem()

    // Modifica baseado no tipo
    switch val.Kind() {
    case reflect.Int:
        val.SetInt(42)
    case reflect.String:
        val.SetString("modificado")
    }
}

func main() {
    x := 10
    modificarValor(&x)
    fmt.Println(x) // 42

    s := "original"
    modificarValor(&s)
    fmt.Println(s) // modificado
}

A conversão segura entre reflect.Value e tipos concretos usa Interface() combinado com type assertions:

func extrairValor(v interface{}) {
    rv := reflect.ValueOf(v)

    // Converte de volta para interface{}
    iface := rv.Interface()

    // Type assertion segura
    if str, ok := iface.(string); ok {
        fmt.Println("String:", str)
    } else if num, ok := iface.(int); ok {
        fmt.Println("Int:", num)
    }
}

4. Reflection em Structs: Campos e Tags

Tags de struct são metadados anexados a campos, frequentemente usados para serialização. Com reflection, podemos lê-los dinamicamente:

type Produto struct {
    Nome  string `json:"nome" validate:"required"`
    Preco float64 `json:"preco" validate:"min=0"`
    Estoque int `json:"estoque,omitempty" validate:"min=0"`
}

func validar(v interface{}) bool {
    t := reflect.TypeOf(v)
    rv := reflect.ValueOf(v)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := rv.Field(i)

        tag := field.Tag.Get("validate")
        if tag == "" {
            continue
        }

        // Exemplo simples de validação
        if tag == "required" {
            if value.String() == "" {
                fmt.Printf("Campo %s é obrigatório\n", field.Name)
                return false
            }
        }

        if tag == "min=0" && value.Float() < 0 {
            fmt.Printf("Campo %s deve ser >= 0\n", field.Name)
            return false
        }
    }
    return true
}

func main() {
    p := Produto{Nome: "Notebook", Preco: -100}
    fmt.Println("Válido:", validar(p)) // false
}

5. Reflection em Funções e Métodos

Podemos inspecionar e chamar funções dinamicamente:

func executarFuncao(fn interface{}, args ...interface{}) []interface{} {
    fv := reflect.ValueOf(fn)
    ft := reflect.TypeOf(fn)

    fmt.Printf("Parâmetros: %d, Retornos: %d\n", 
        ft.NumIn(), ft.NumOut())

    // Prepara argumentos
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }

    // Chama a função
    resultados := fv.Call(in)

    // Converte resultados
    out := make([]interface{}, len(resultados))
    for i, r := range resultados {
        out[i] = r.Interface()
    }
    return out
}

func soma(a, b int) int {
    return a + b
}

func main() {
    resultado := executarFuncao(soma, 10, 20)
    fmt.Println("Resultado:", resultado[0]) // 30
}

6. Performance e Boas Práticas

Reflection é significativamente mais lenta que código direto porque:

  • Alocações extras: reflect.Value e reflect.Type são structs que precisam ser alocadas
  • Boxing/unboxing: valores são convertidos para interface{} e depois extraídos
  • Falta de otimização do compilador: o compilador não pode inline ou otimizar chamadas via reflection

Alternativas para evitar reflection:

// RUIM: reflection para cada tipo
func processarReflection(v interface{}) {
    rv := reflect.ValueOf(v)
    for i := 0; i < rv.Len(); i++ {
        // processa
    }
}

// BOM: type switch
func processarTypeSwitch(v interface{}) {
    switch v := v.(type) {
    case []int:
        for _, item := range v { /* processa */ }
    case []string:
        for _, item := range v { /* processa */ }
    }
}

// MELHOR (Go 1.18+): generics
func processarGenerics[T any](v []T) {
    for _, item := range v { /* processa */ }
}

7. Casos de Uso Avançados e Armadilhas

Criando um marshal simples:

func marshalSimples(v interface{}) (map[string]interface{}, error) {
    rv := reflect.ValueOf(v)
    rt := reflect.TypeOf(v)

    if rt.Kind() == reflect.Ptr {
        rv = rv.Elem()
        rt = rt.Elem()
    }

    if rt.Kind() != reflect.Struct {
        return nil, fmt.Errorf("esperava struct, got %s", rt.Kind())
    }

    resultado := make(map[string]interface{})
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        value := rv.Field(i)

        // Ignora campos não exportados
        if !field.IsExported() {
            continue
        }

        resultado[field.Name] = value.Interface()
    }
    return resultado, nil
}

Armadilhas comuns:

  • Ponteiros vs valores: sempre use Elem() para desreferenciar ponteiros
  • Campos não exportados: CanSet() retorna false para campos privados
  • Comparação de nil: reflect.ValueOf(nil) retorna um valor inválido

8. Conclusão e Referências

Reflection é uma ferramenta poderosa em Go, mas deve ser usada com moderação. Prefira sempre type switches, interfaces e generics quando possível. Use reflection apenas quando não houver alternativa viável, como em bibliotecas de serialização ou frameworks que precisam lidar com tipos desconhecidos em tempo de compilação.

Com o advento dos generics (Go 1.18+), muitos casos de uso de reflection podem ser substituídos por código mais seguro e performático. No entanto, reflection ainda é essencial para metaprogramação e ferramentas que operam em tipos arbitrários.

Referências