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/jsoneencoding/xmlusam 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 Goreflect.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.Valueereflect.Typesã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()retornafalsepara 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
- Documentação oficial do pacote reflect — Referência completa de todos os tipos e funções do pacote reflect em Go
- The Laws of Reflection (Blog oficial Go) — Artigo fundamental de Rob Pike explicando os três princípios da reflection em Go
- Using reflection in Go (DigitalOcean) — Tutorial prático cobrindo desde conceitos básicos até exemplos avançados
- Reflection in Go: A Practical Guide (Medium) — Guia prático com exemplos do mundo real e armadilhas comuns
- Go Reflection Code Patterns (Ardan Labs) — Padrões de código e boas práticas para usar reflection em Go
- Understanding Go's Reflection Package (TutorialEdge) — Tutorial detalhado com exemplos de uso em serialização e validação
- Go 1.18 Generics vs Reflection (Earthly Blog) — Comparação entre generics e reflection com benchmarks de performance