Reflection
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.
Intuition
Section titled “Intuition”Reflection is the runtime looking in a mirror: Normally, Go’s type system knows everything at compile time — what’s an int, what’s a string, what fields a struct has. Reflection is the ability to ask “what am I?” at runtime. It’s like opening a box and reading its label instead of knowing what’s inside from the packing slip. Struct tags are sticky notes on those boxes that serialization libraries read to know how to pack/unpack them.
Why it matters: Reflection enables libraries that work with arbitrary types — JSON encoders, ORM mappers, validation frameworks — without requiring code generation. It’s the price of admission for writing truly generic code in a statically typed language.
The key insight: Reflection is powerful but expensive — it trades compile-time safety and performance for runtime flexibility. Use generics or type assertions first; reach for reflection only when you genuinely don’t know the type.
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.
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.
Cross-References
Section titled “Cross-References”- Interfaces: Interface satisfaction and type assertions underlying reflection.
- Generics: Compile-time type parameters as an alternative to runtime reflection.
- Types and Variables: Go’s type system fundamentals that reflection inspects at runtime.