πŸͺž 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.

⏱ β€”πŸ“Š Advanced

🎯 Learning Objectives

βœ… After this chapter you will:
  • Use reflect.TypeOf and reflect.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)
  1. Reflection goes from interface value to reflection object: reflect.TypeOf(x) and reflect.ValueOf(x) examine the concrete type stored in an interface.
  2. Reflection goes from reflection object to interface value: v.Interface() converts a reflect.Value back to an interface{}.
  3. 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 frameworksPerformance is critical (reflection is ~10–100Γ— slower)
Reading struct tags for validation/ORMSimple polymorphism (use interfaces instead)
Test utilities (deep equality, spy objects)The code would be readable without it
Dynamic dispatch for pluginsYou 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.Struct
Struct 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 addressable
Call function
fv.Call([]reflect.Value{...})
Call method
v.MethodByName("Name").Call(...)
To interface{}
v.Interface()
DeepEqual
reflect.DeepEqual(a, b) // for slices/maps