Go Reflection Tutorial
Reflection in Go (via the reflect package) enables programs to examine and manipulate objects at runtime. This powerful but advanced feature is used for tasks like serialization, ORMs, and dependency injection.
1. Reflection Basics
1.1 Type and Value
The core of reflection is the reflect.Type and reflect.Value types:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
t := reflect.TypeOf(x) // reflect.Type
v := reflect.ValueOf(x) // reflect.Value
fmt.Println("Type:", t) // "float64"
fmt.Println("Value:", v) // "3.14"
fmt.Println("Kind:", v.Kind()) // "float64"
}
Key Concepts:
- TypeOf: Gets the static type information
- ValueOf: Gets the runtime value information
- Kind(): Returns the underlying primitive type
2. Inspecting Values
2.1 Working with Kinds
Different kinds require different handling:
func inspectValue(v reflect.Value) {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println("Integer:", v.Int())
case reflect.Float32, reflect.Float64:
fmt.Println("Float:", v.Float())
case reflect.String:
fmt.Println("String:", v.String())
case reflect.Bool:
fmt.Println("Bool:", v.Bool())
default:
fmt.Println("Unsupported kind:", v.Kind())
}
}
2.2 Accessing Struct Fields
Reflectively access struct fields and tags:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
u := User{"Alice", 30}
v := reflect.ValueOf(u)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s (%s) = %v, tag: %s\n",
field.Name,
field.Type,
value.Interface(),
field.Tag.Get("json"))
}
}
3. Modifying Values
3.1 Settable Values
Values must be addressable to be modified:
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x).Elem() // Note: & and Elem()
if v.CanSet() {
v.SetFloat(6.28)
fmt.Println(x) // 6.28
}
}
3.2 Modifying Struct Fields
Change struct field values reflectively:
type Config struct {
Host string
Port int
}
func main() {
cfg := &Config{"localhost", 8080}
v := reflect.ValueOf(cfg).Elem()
hostField := v.FieldByName("Host")
if hostField.IsValid() && hostField.CanSet() {
hostField.SetString("example.com")
}
fmt.Println(cfg) // &{example.com 8080}
}
4. Calling Functions
4.1 Dynamic Function Invocation
Call functions by name using reflection:
func Add(a, b int) int {
return a + b
}
func main() {
v := reflect.ValueOf(Add)
args := []reflect.Value{
reflect.ValueOf(3),
reflect.ValueOf(5),
}
results := v.Call(args)
sum := results[0].Int()
fmt.Println(sum) // 8
}
4.2 Method Invocation
Call methods on structs dynamically:
type Calculator struct{}
func (c Calculator) Multiply(x, y int) int {
return x * y
}
func main() {
calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Multiply")
result := method.Call([]reflect.Value{
reflect.ValueOf(4),
reflect.ValueOf(5),
})
fmt.Println(result[0].Int()) // 20
}
5. Advanced Patterns
5.1 Creating New Values
Instantiate types at runtime:
func main() {
t := reflect.TypeOf(User{})
v := reflect.New(t).Elem() // Creates zero value
// Set fields
v.FieldByName("Name").SetString("Bob")
v.FieldByName("Age").SetInt(25)
user := v.Interface().(User)
fmt.Println(user) // {Bob 25}
}
5.2 Working with Slices and Maps
Dynamically manipulate collections:
func main() {
// Create slice dynamically
sliceType := reflect.SliceOf(reflect.TypeOf(0))
slice := reflect.MakeSlice(sliceType, 0, 10)
slice = reflect.Append(slice, reflect.ValueOf(1), reflect.ValueOf(2))
// Create map dynamically
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
m := reflect.MakeMap(mapType)
key := reflect.ValueOf("answer")
value := reflect.ValueOf(42)
m.SetMapIndex(key, value)
fmt.Println(slice.Interface()) // [1 2]
fmt.Println(m.Interface()) // map[answer:42]
}
6. Best Practices
6.1 When to Use Reflection
- Appropriate uses: Serialization, ORMs, dependency injection
- Avoid when: Static code would work, performance-critical paths
6.2 Performance Considerations
// ❌ Slow: Reflection in hot loop
func slowReflection(items []interface{}) {
for _, item := range items {
v := reflect.ValueOf(item)
// ... reflection operations
}
}
// ✅ Better: Cache reflect.Type and use type switches
var userType = reflect.TypeOf(User{})
func fastProcessing(items []interface{}) {
for _, item := range items {
switch v := item.(type) {
case User:
// Direct access
case int:
// Direct access
}
}
}
6.3 Safety Checks
func safeReflection(v reflect.Value) {
// Always check these before operations:
if !v.IsValid() {
panic("Invalid value")
}
if v.Kind() != reflect.Int {
panic("Wrong kind")
}
if !v.CanSet() {
panic("Can't set value")
}
}
×