Introduction
Reflection allows a Go program to inspect and manipulate its own types and values at runtime. It is the mechanism behind json.Marshal, fmt.Println, and ORM libraries. While powerful, reflection trades compile-time safety for runtime flexibility. Understanding it is essential for building frameworks and understanding how Go's standard library works.
Key Concepts
- reflect.TypeOf(): Returns the dynamic type of a value as a
reflect.Type. - reflect.ValueOf(): Returns the runtime value wrapped in a
reflect.Value. - Kind vs Type: Kind is the underlying category (int, struct, slice), while Type is the specific named type (MyInt, User).
Real World Context
You are building a custom validation library that reads struct tags and validates field values. Without reflection, you would need to write validation logic for every struct individually. With reflection, you write it once and it works for any struct.
Deep Dive
The two fundamental operations are getting a type and getting a value:
govar x float64 = 3.14 t := reflect.TypeOf(x) v := reflect.ValueOf(x) fmt.Println(t) // float64 fmt.Println(v) // 3.14 fmt.Println(t.Kind()) // float64 fmt.Println(v.Float()) // 3.14
Kind and Type are different concepts. Type is the specific named type, while Kind is the underlying category:
gotype MyInt int var x MyInt = 42 t := reflect.TypeOf(x) fmt.Println(t.Name()) // MyInt (the specific type) fmt.Println(t.Kind()) // int (the underlying kind)
You can inspect struct fields using NumField and Field:
gofunc printFields(x any) { v := reflect.ValueOf(x) t := v.Type() for i := 0; i < v.NumField(); i++ { fmt.Printf("Field: %s\tValue: %v\n", t.Field(i).Name, v.Field(i).Interface()) } }
Go 1.26 adds new iterator methods to reflect.Type and reflect.Value: .Fields(), .Methods(), .Ins(), .Outs(), and reflect.Value.Fields(), .Methods(). These return iterators and are more idiomatic than the old NumField/Field(i) loop pattern:
go// Go 1.26 idiomatic iteration for field := range reflect.TypeOf(User{}).Fields() { fmt.Println(field.Name) }
Common Pitfalls
- Confusing Kind and Type —
reflect.TypeOf(MyInt(42)).Kind()isint, notMyInt. Use.Name()for the specific type. - Calling NumField on non-struct types — This panics. Always check
v.Kind() == reflect.Structfirst.
Best Practices
- Check Kind before accessing type-specific methods — Methods like NumField, Len, and MapKeys are only valid for specific Kinds.
- Use the new Go 1.26 iterator methods —
.Fields()and.Methods()are cleaner than manual index loops.
Summary
reflect.TypeOf()andreflect.ValueOf()are the entry points to reflection.- Kind is the underlying category (int, struct); Type is the specific named type.
- Go 1.26 adds
.Fields(),.Methods()iterator methods for idiomatic iteration. - Always check Kind before calling type-specific reflection methods.
Code Examples
// printFields uses reflection to print all fields of any struct.
// It works with any struct type without knowing the fields at compile time.
func printFields(x any) {
v := reflect.ValueOf(x)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
fmt.Printf("Field: %s\tValue: %v\n",
t.Field(i).Name, v.Field(i).Interface())
}
}
// Usage: printFields(User{Name: "Alice", Age: 30})
// Output:
// Field: Name Value: Alice
// Field: Age Value: 30