Reflection | Go - Wyatt's Notes
Overview
Section titled “Overview”The reflect package provides runtime type introspection and manipulation. It allows programs to Examine types, inspect struct fields, call methods by name, and modify values dynamically.
Reflection is powerful but slow and type-unsafe. Use it only when static typing is insufficient: Serialization, ORMs, configuration parsing, testing frameworks.
reflect.Type and reflect.Value
Section titled “reflect.Type and reflect.Value”The two core types:
reflect.Typerepresents a Go type (int, string, struct, slice, etc.)reflect.Valuerepresents a value of any type
func inspect(i any) { t := reflect.TypeOf(i) v := reflect.ValueOf(i)
fmt.Println("Type:", t) fmt.Println("Kind:", t.Kind()) fmt.Println("Value:", v)}
inspect(42) // Type: int, Kind: int, Value: 42inspect("hello") // Type: string, Kind: string, Value: helloinspect(3.14) // Type: float64, Kind: float64, Value: 3.14Kind vs Type
Section titled “Kind vs Type”Kind is the underlying category. Type is the concrete type:
type MyInt int
var x MyInt = 42t := reflect.TypeOf(x)
fmt.Println(t.Name()) // MyIntfmt.Println(t.Kind()) // intKind is one of: Bool``Int``Int8``Int16``Int32``Int64``Uint``Uint8``Uint16 Uint32``Uint64``Float32``Float64``Complex64``Complex128``String``Array``Slice Map``Chan``Func``Interface``Struct``Ptr``UnsafePointer.
Modifying Values
Section titled “Modifying Values”reflect.ValueOf returns a non-settable value (a copy). To modify the original, pass a pointer:
x := 42v := reflect.ValueOf(&x).Elem() // dereference the pointer to get a settable valuev.SetInt(100)fmt.Println(x) // 100CanSet() indicates whether a value can be modified:
v := reflect.ValueOf(x)fmt.Println(v.CanSet()) // false -- value was copied
v = reflect.ValueOf(&x).Elem()fmt.Println(v.CanSet()) // true -- points to the originalStruct Reflection
Section titled “Struct Reflection”Inspecting Fields
Section titled “Inspecting Fields”type Person struct { Name string `json:"name" validate:"required"` Age int `json:"age" validate:"min=0"`}
p := Person{Name: "Alice", Age: 30}t := reflect.TypeOf(p)
for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("Field: %s, Type: %s, Tag: %s\n", field.Name, field.Type, field.Tag.Get("json"))}// Field: Name, Type: string, Tag: name// Field: Age, Type: int, Tag: ageSetting Fields by Name
Section titled “Setting Fields by Name”p := Person{}v := reflect.ValueOf(&p).Elem()
nameField := v.FieldByName("Name")if nameField.IsValid() && nameField.CanSet() { nameField.SetString("Bob")}
ageField := v.FieldByName("Age")if ageField.IsValid() && ageField.CanSet() { ageField.SetInt(25)}
fmt.Println(p) // {Bob 25}Struct Tags
Section titled “Struct Tags”Struct tags are key-value metadata attached to struct fields. They are conventionally used by Libraries for serialization, validation, and ORM mapping:
type Config struct { Host string `toml:"host" default:"localhost"` Port int `toml:"port" default:"8080"` Debug bool `toml:"debug" default:"false"`}Access tags via reflection:
field, _, _ := reflect.TypeOf(Config{}).FieldByName("Host")tag := field.Tagfmt.Println(tag.Get("toml")) // "host"fmt.Println(tag.Get("default")) // "localhost"Slice, Map, and Function Reflection
Section titled “Slice, Map, and Function Reflection”Creating Slices
Section titled “Creating Slices”sliceType := reflect.TypeOf([]int{})slice := reflect.MakeSlice(sliceType, 0, 10)slice = reflect.Append(slice, reflect.ValueOf(1))slice = reflect.Append(slice, reflect.ValueOf(2))fmt.Println(slice.Interface()) // [1 2]Creating Maps
Section titled “Creating Maps”mapType := reflect.TypeOf(map[string]int{})m := reflect.MakeMap(mapType)m.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))m.SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf(2))fmt.Println(m.Interface()) // map[a:1 b:2]Calling Functions
Section titled “Calling Functions”fn := reflect.ValueOf(strings.ToUpper)result := fn.Call([]reflect.Value{reflect.ValueOf("hello")})fmt.Println(result[0].Interface()) // "HELLO"For functions with variadic arguments, use CallSlice:
fn := reflect.ValueOf(fmt.Sprintf)result := fn.Call([]reflect.Value{ reflect.ValueOf("%s %d"), reflect.ValueOf("count"), reflect.ValueOf(42),})Interface to Concrete
Section titled “Interface to Concrete”Convert an any to a concrete type using reflection:
func ToInt(i any) (int, bool) { v := reflect.ValueOf(i) if v.Kind() == reflect.Int { return int(v.Int()), true } return 0, false}Prefer type assertions over reflection when the type is known at compile time:
// Better: type assertionn, ok := i.(int)
// Worse: reflection (slower, no compile-time checking)v := reflect.ValueOf(i)Implementing a Generic Formatter
Section titled “Implementing a Generic Formatter”Reflection enables generic processing of arbitrary types:
func PrintFields(v any) { val := reflect.ValueOf(v) if val.Kind() == reflect.Ptr { val = val.Elem() } if val.Kind() != reflect.Struct { fmt.Println("not a struct") return }
typ := val.Type() for i := 0; i < val.NumField(); i++ { field := typ.Field(i) value := val.Field(i) fmt.Printf("%s (%s): %v\n", field.Name, field.Type, value.Interface()) }}Performance
Section titled “Performance”Reflection is significantly slower than direct access:
// Direct: ~1nsp.Name = "Alice"
// Reflection: ~100ns (100x slower)reflect.ValueOf(&p).Elem().FieldByName("Name").SetString("Alice")If performance is critical, avoid reflection. Consider code generation (text/template) or generics As alternatives.
Common Pitfalls
Section titled “Common Pitfalls”Reflecting on unexported fields. Reflection cannot read or set unexported (lowercase) struct fields.
CanSet()returns false for unexported fields.Forgetting
Elem()for pointer values.reflect.ValueOf(&x)gives you a*intValue. Call.Elem()to get the underlyingintValue for modification.Panicking on wrong types.
Int()panics if the kind is not an integer. Always checkKind()before calling type-specific methods.Performance in hot paths. Reflection is 10-100x slower than direct code. Do not use it in tight loops. Cache
reflect.Typeandreflect.Valueresults when possible.Using reflection when type assertions suffice. If you know the possible types at compile time, use type switches and type assertions. They are type-safe and fast.
Modifying unexported fields. This is not possible through the
reflectpackage. If you must do it, useunsafe(but this is highly discouraged and may break across Go versions).Ignoring
CanAddrandCanSet. Not allreflect.Valueobjects are addressable or settable. Always check before attempting modification.
flowchart TD
A[Reflection] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]Summary
Section titled “Summary”This topic covers the core concepts of reflection, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Intuition
Section titled “Intuition”Reflection lets you inspect and manipulate types at runtime when static typing is not enough. Think of it as a mirror that shows you the shape of any value — its type, its fields, its methods. The reflect.Value and reflect.Type types are the two faces of this mirror. You can only modify values obtained through a pointer (pass a pointer, then call .Elem() to get a settable value). Struct tags are metadata annotations read by reflection-based libraries for serialization and validation. Reflection is powerful but slow — about 100 times slower than direct field access — so prefer type assertions and generics when the type is known at compile time.
Cross-References
Section titled “Cross-References”- Types and Variables — type system and conversions
- Functions — first-class values and closures
- Goroutines and Synchronization — concurrency primitives