πͺ Chapter 18 of 20
Reflection
Reflection lets Go programs inspect and modify their own structure at runtime. It powers encoding/json, ORMs, dependency injection frameworks, and test utilities. It's powerful β and must be used carefully.
π― Learning Objectives
β
After this chapter you will:
- Use
reflect.TypeOfandreflect.ValueOf - Inspect struct fields and tags at runtime
- Set values dynamically using reflection
- Call functions and methods dynamically
- Understand the three laws of reflection
- Know when NOT to use reflection (and use generics instead)
- Understand how encoding/json uses reflection internally
βοΈ The Three Laws of Reflection
π Three Laws (from Rob Pike)
- Reflection goes from interface value to reflection object:
reflect.TypeOf(x)andreflect.ValueOf(x)examine the concrete type stored in an interface. - Reflection goes from reflection object to interface value:
v.Interface()converts areflect.Valueback to aninterface{}. - To modify a reflection object, the value must be settable: you must pass a pointer (
reflect.ValueOf(&x).Elem()) to get a settable Value.
π reflect.TypeOf & reflect.ValueOf
go
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=0,max=150"`
}
func main() {
x := 42
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("Type:", t) // int
fmt.Println("Kind:", t.Kind()) // int
fmt.Println("Value:", v) // 42
fmt.Println("Value.Int():", v.Int()) // 42
// Inspect a struct
p := Person{Name: "Alice", Age: 30}
pt := reflect.TypeOf(p)
pv := reflect.ValueOf(p)
fmt.Printf("\nStruct: %s\n", pt.Name())
for i := 0; i < pt.NumField(); i++ {
field := pt.Field(i) // reflect.StructField
value := pv.Field(i) // reflect.Value
jsonTag := field.Tag.Get("json")
validateTag := field.Tag.Get("validate")
fmt.Printf(" Field %d: %s (%s)\n", i, field.Name, field.Type)
fmt.Printf(" Value: %v\n", value)
fmt.Printf(" json tag: %q\n", jsonTag)
fmt.Printf(" validate: %q\n", validateTag)
}
// Type comparisons
fmt.Println("\nIs struct:", pt.Kind() == reflect.Struct) // true
fmt.Println("Is ptr:", reflect.TypeOf(&p).Kind() == reflect.Ptr) // true
// Inspect a slice
s := []int{1, 2, 3}
sv := reflect.ValueOf(s)
fmt.Printf("\nSlice len=%d cap=%d\n", sv.Len(), sv.Cap())
for i := 0; i < sv.Len(); i++ {
fmt.Printf(" s[%d] = %v\n", i, sv.Index(i))
}
}β
Output
Type: int
Kind: int
Value: 42
Value.Int(): 42
Struct: Person
Field 0: Name (string)
Value: Alice
json tag: "name"
validate: "required"
Field 1: Age (int)
Value: 30
json tag: "age"
validate: "min=0,max=150"
Is struct: true
Is ptr: true
Slice len=3 cap=3
s[0] = 1
s[1] = 2
s[2] = 3
βοΈ Setting Values with Reflection
go
package main
import (
"fmt"
"reflect"
)
type Config struct {
Host string
Port int
Debug bool
Timeout float64
}
// SetField sets a struct field by name using reflection
func SetField(obj interface{}, name string, value interface{}) error {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("obj must be a pointer to a struct")
}
v = v.Elem() // dereference the pointer
field := v.FieldByName(name)
if !field.IsValid() {
return fmt.Errorf("no field %q in struct", name)
}
if !field.CanSet() {
return fmt.Errorf("field %q is not settable (unexported?)", name)
}
fv := reflect.ValueOf(value)
if field.Type() != fv.Type() {
return fmt.Errorf("field %q has type %v, cannot set with %v", name, field.Type(), fv.Type())
}
field.Set(fv)
return nil
}
func main() {
cfg := &Config{}
SetField(cfg, "Host", "localhost")
SetField(cfg, "Port", 8080)
SetField(cfg, "Debug", true)
SetField(cfg, "Timeout", 30.0)
fmt.Printf("%+v\n", *cfg)
// {Host:localhost Port:8080 Debug:true Timeout:30}
// Direct approach: must pass pointer
x := 10
v := reflect.ValueOf(&x).Elem() // get settable Value
fmt.Println("Settable:", v.CanSet()) // true
v.SetInt(99)
fmt.Println("x =", x) // 99
// y := 10
// reflect.ValueOf(y).SetInt(99) // β panic: not settable!
}β
Output
{Host:localhost Port:8080 Debug:true Timeout:30}
Settable: true
x = 99
π Calling Functions Dynamically
go
package main
import (
"fmt"
"reflect"
)
func Greet(name string) string {
return "Hello, " + name + "!"
}
func Add(a, b int) int { return a + b }
type Calculator struct{ Name string }
func (c Calculator) Multiply(a, b int) int { return a * b }
func callFunc(fn interface{}, args ...interface{}) []interface{} {
fv := reflect.ValueOf(fn)
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
out := fv.Call(in)
results := make([]interface{}, len(out))
for i, v := range out {
results[i] = v.Interface()
}
return results
}
func main() {
// Call regular function
results := callFunc(Greet, "Alice")
fmt.Println(results[0]) // Hello, Alice!
results = callFunc(Add, 3, 4)
fmt.Println(results[0]) // 7
// Call method
calc := Calculator{Name: "myCalc"}
mv := reflect.ValueOf(calc).MethodByName("Multiply")
out := mv.Call([]reflect.Value{
reflect.ValueOf(6),
reflect.ValueOf(7),
})
fmt.Println(out[0].Int()) // 42
// Inspect function type
ft := reflect.TypeOf(Add)
fmt.Printf("Func: %v β %d in, %d out\n", ft, ft.NumIn(), ft.NumOut())
for i := 0; i < ft.NumIn(); i++ {
fmt.Printf(" arg%d: %v\n", i, ft.In(i))
}
}β
Output
Hello, Alice!
7
42
Func: func(int, int) int β 2 in, 1 out
arg0: int
arg1: int
π« When to Avoid Reflection
| β Use Reflection When | β Avoid Reflection When |
|---|---|
| Building serialisation (JSON, YAML, XML) | You know all types at compile time (use generics) |
| Dependency injection frameworks | Performance is critical (reflection is ~10β100Γ slower) |
| Reading struct tags for validation/ORM | Simple polymorphism (use interfaces instead) |
| Test utilities (deep equality, spy objects) | The code would be readable without it |
| Dynamic dispatch for plugins | You would need unsafe operations alongside it |
β οΈ Reflection Costs
- Type checks at compile time become panics at runtime
- ~10β100Γ slower than direct code
- Defeats the compiler's ability to optimise
- Code becomes hard to understand and refactor
The Go team says: "Reflection is a powerful tool, but it should be used with care. Clear code is better than clever code."
π§ͺ Quiz
Reflection
4 questions
1. What do
reflect.TypeOf(x) and reflect.ValueOf(x) return?2. Why must you pass a pointer when setting values via reflection?
3. How do you read a struct tag in reflection?
4. Which is a valid reason to AVOID using reflection?
β
π Cheat Sheet
π Reflection β Quick Reference
Get type
reflect.TypeOf(x)Get value
reflect.ValueOf(x)Kind
reflect.TypeOf(x).Kind() == reflect.StructStruct fields
t.NumField(); t.Field(i)Field value
v.Field(i).Interface()Read tag
t.Field(i).Tag.Get("json")Set value
reflect.ValueOf(&x).Elem().Set(...)CanSet check
v.CanSet() // must be addressableCall function
fv.Call([]reflect.Value{...})Call method
v.MethodByName("Name").Call(...)To interface{}
v.Interface()DeepEqual
reflect.DeepEqual(a, b) // for slices/maps