reflect.Type & reflect.Value
TypeOf ยท ValueOf ยท KindEvery Go value has two components in reflection: var x float64 = 3.14 reflect.TypeOf(x) โ reflect.Type (describes the type: float64) reflect.ValueOf(x) โ reflect.Value (holds the value: 3.14) Type.Kind() โ the underlying kind: Bool, Int, Float64, Struct, Slice, โฆ Type.Name() โ the declared type name: "float64", "MyStruct", ""(unnamed)
TypeOf and ValueOf basics
Type / Value
import "reflect" var x float64 = 3.14 t := reflect.TypeOf(x) fmt.Println(t.Name()) // float64 fmt.Println(t.Kind()) // float64 (same for primitives) v := reflect.ValueOf(x) fmt.Println(v.Type()) // float64 fmt.Println(v.Kind()) // float64 fmt.Println(v.Float()) // 3.14 (panics if not float kind) // Getting the concrete value back out var result float64 = v.Interface().(float64)
Kind vs Type
Kind
type Celsius float64 var c Celsius = 100 t := reflect.TypeOf(c) t.Name() // "Celsius" โ declared name t.Kind() // float64 โ underlying kind // Kind is useful for generic operations: // switch on Kind to handle all int-like, all slice-like, etc. // Type is useful for exact type identity.
Kind switch โ handle any type
switch
func describe(i any) string { v := reflect.ValueOf(i) switch v.Kind() { case reflect.Int, reflect.Int64: return fmt.Sprintf("int: %d", v.Int()) case reflect.String: return fmt.Sprintf("string: %q", v.String()) case reflect.Slice: return fmt.Sprintf("slice len=%d", v.Len()) default: return v.Type().String() } }
Inspecting Structs
NumField ยท Field ยท FieldByName
Iterating struct fields
NumField
type Person struct { Name string Age int Email string } p := Person{"Alice", 30, "alice@example.com"} t := reflect.TypeOf(p) v := reflect.ValueOf(p) for i := 0; i < t.NumField(); i++ { field := t.Field(i) // reflect.StructField value := v.Field(i) // reflect.Value fmt.Printf("%s = %v\n", field.Name, value.Interface()) } // Name = Alice // Age = 30 // Email = alice@example.com
Field by name
FieldByName
t := reflect.TypeOf(p) f, ok := t.FieldByName("Age") if ok { fmt.Println(f.Name) // Age fmt.Println(f.Type) // int fmt.Println(f.Index) // [1] } v := reflect.ValueOf(p) fmt.Println(v.FieldByName("Name")) // Alice
Exported vs unexported fields
IsExported
type Config struct { Host string // exported timeout int // unexported } t := reflect.TypeOf(Config{}) for i := 0; i < t.NumField(); i++ { f := t.Field(i) if f.IsExported() { fmt.Println(f.Name, "is exported") } } // You cannot call .Interface() on unexported fields // โ it panics. Check IsExported first.
Struct Tags
StructTag ยท Get ยท LookupStruct tags are raw string literals attached to struct fields. They are invisible at runtime unless accessed via reflection. Standard packages like
encoding/json, database/sql, and gopkg.in/yaml.v3 read them to control serialization.
Reading struct tags
StructTag.Get
type User struct { Name string `json:"name" validate:"required,min=2"` Email string `json:"email,omitempty" validate:"required,email"` age int `json:"-"` // unexported, json ignores it } t := reflect.TypeOf(User{}) f, _ := t.FieldByName("Email") tag := f.Tag // reflect.StructTag tag.Get("json") // "email,omitempty" tag.Get("validate") // "required,email" // Lookup returns (value, ok) โ prefer over Get to distinguish // missing tag from empty tag value val, ok := tag.Lookup("json") // "email,omitempty", true _, ok = tag.Lookup("xml") // "", false
Building a simple struct-to-map encoder
Use case
// Encode a struct to map[string]any using json tags as keys func structToMap(v any) map[string]any { out := make(map[string]any) rv := reflect.ValueOf(v) rt := rv.Type() for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) if !f.IsExported() { continue } key := f.Name if tag, ok := f.Tag.Lookup("json"); ok && tag != "-" { key = strings.Split(tag, ",")[0] } out[key] = rv.Field(i).Interface() } return out }
Modifying Values
CanSet ยท Elem ยท pointer requiredYou can only set a value through reflection if it is addressable. To make a value addressable, pass a pointer to
reflect.ValueOf and call .Elem() to get the pointed-at value. Calling .Set() on a non-addressable value panics.
Setting a struct field via reflection
CanSet ยท Set
type Config struct { Timeout int Debug bool } cfg := &Config{} // Must pass pointer and call Elem() to get addressable value rv := reflect.ValueOf(cfg).Elem() rv.FieldByName("Timeout").SetInt(30) rv.FieldByName("Debug").SetBool(true) fmt.Println(*cfg) // {30 true} // Check first to avoid panics on unexported or read-only fields f := rv.FieldByName("Timeout") if f.CanSet() { f.SetInt(60) }
New โ create a value dynamically
reflect.New
// reflect.New(T) allocates a *T and returns reflect.Value t := reflect.TypeOf(Config{}) ptr := reflect.New(t) // reflect.Value of *Config ptr.Elem().FieldByName("Debug").SetBool(true) // Convert back to concrete type cfg := ptr.Interface().(*Config) fmt.Println(cfg.Debug) // true
Elem() โ dereference pointer or interface
Elem
x := 42 p := &x // ValueOf(p) is a reflect.Value of kind Ptr vp := reflect.ValueOf(p) fmt.Println(vp.Kind()) // ptr // .Elem() follows the pointer ve := vp.Elem() fmt.Println(ve.Kind()) // int fmt.Println(ve.CanSet()) // true ve.SetInt(99) fmt.Println(x) // 99
Calling Functions & Methods
Call ยท MethodByName ยท In ยท Out
Inspecting and calling a function
reflect.Value.Call
func Add(a, b int) int { return a + b } ft := reflect.TypeOf(Add) fmt.Println(ft.NumIn(), ft.NumOut()) // 2 1 fmt.Println(ft.In(0)) // int fmt.Println(ft.Out(0)) // int fv := reflect.ValueOf(Add) args := []reflect.Value{ reflect.ValueOf(3), reflect.ValueOf(4), } results := fv.Call(args) fmt.Println(results[0].Int()) // 7
Calling a method by name
MethodByName
type Greeter struct{ Name string } func (g Greeter) Hello() string { return "Hello, " + g.Name } g := Greeter{Name: "Alice"} v := reflect.ValueOf(g) m := v.MethodByName("Hello") if !m.IsValid() { fmt.Println("method not found") return } result := m.Call(nil) fmt.Println(result[0].String()) // Hello, Alice
reflect.DeepEqual
equality ยท slices ยท maps ยท testingreflect.DeepEqual recursively compares two values, including slices, maps, structs, and pointers. It is most useful in tests where == won't work (slices, maps). For production code prefer cmp.Equal from google/go-cmp โ it gives better diff output.
DeepEqual examples
DeepEqual
// Slices โ == doesn't compile, DeepEqual works a := []int{1, 2, 3} b := []int{1, 2, 3} reflect.DeepEqual(a, b) // true // Maps m1 := map[string]int{"a": 1} m2 := map[string]int{"a": 1} reflect.DeepEqual(m1, m2) // true // nil vs empty slice โ NOT equal reflect.DeepEqual([]int{}, ([]int)(nil)) // false!
DeepEqual gotchas
Gotchas
// Struct with unexported fields: DeepEqual CAN compare // but cmp.Equal panics unless you configure it // Function values: only equal if both nil reflect.DeepEqual(fmt.Println, fmt.Println) // false! // Pointer comparison: follows pointers and compares // pointed-at values, not the addresses x, y := 42, 42 reflect.DeepEqual(&x, &y) // true (same value) // NaN != NaN even with DeepEqual reflect.DeepEqual(math.NaN(), math.NaN()) // false
Pitfalls & When to Avoid Reflection
performance ยท panics ยท alternativesReflection bypasses the type system โ errors that the compiler would catch become runtime panics. Code using reflection is harder to read, harder to refactor, and significantly slower than direct code. Use it only when generics or interfaces won't work.
Common reflection panics
Panics
// 1. Calling a type-specific extractor on the wrong kind v := reflect.ValueOf("hello") v.Int() // panic: call of reflect.Value.Int on string Value // 2. Setting a non-addressable value v = reflect.ValueOf(42) // not addressable (copy) v.SetInt(99) // panic: reflect: reflect.Value.SetInt using value obtained using unexported field // 3. Calling Interface() on unexported struct field type S struct{ secret int } reflect.ValueOf(S{1}).Field(0).Interface() // panic // 4. Nil pointer dereference var p *int reflect.ValueOf(p).Elem() // panic: reflect: call of reflect.Value.Elem on zero Value
When to use reflection โ and when not to
Guidelines
// โ USE reflection for: // โข Serialization / deserialization libraries (encoding/json, yaml) // โข ORM / query builders (field โ column mapping) // โข Dependency injection frameworks // โข Generic testing utilities (DeepEqual, diff) // โข Reading struct tags to drive behaviour // โ AVOID reflection when: // โข You could use generics (Go 1.18+) instead // โข You could use an interface with a defined method set // โข Performance is critical (reflection is ~10-100x slower) // โข You control all the types involved // Rule of thumb: if the caller controls the concrete type, // use generics. If the library doesn't know the type at // compile time, use reflection.
Prefer generics for type-safe operations
Generics first
// Reflection โ works but no compile-time safety func containsReflect(slice, item any) bool { sv := reflect.ValueOf(slice) iv := reflect.ValueOf(item) for i := 0; i < sv.Len(); i++ { if reflect.DeepEqual(sv.Index(i).Interface(), iv.Interface()) { return true } } return false } // Generics โ type-safe, faster, clearer func contains[T comparable](slice []T, item T) bool { for _, v := range slice { if v == item { return true } } return false }
Performance cost of reflection
Performance
// Direct field access: ~1 ns/op cfg.Timeout = 30 // Reflection field set: ~50-200 ns/op // (type lookup, boundary checks, allocation) rv.FieldByName("Timeout").SetInt(30) // Mitigations: // Cache reflect.Type and field indices at init time var timeoutIdx = reflect.TypeOf(Config{}).Field(0).Index[0] // Use Field(i) with cached index instead of FieldByName rv.Field(timeoutIdx).SetInt(30) // ~3x faster
Quick Reference
Cheat-sheet| Concept | Call | Notes |
|---|---|---|
| Get type | reflect.TypeOf(x) | Returns reflect.Type |
| Get value | reflect.ValueOf(x) | Returns reflect.Value |
| Type name | t.Name() | Declared name; "" for anonymous types |
| Underlying kind | v.Kind() | Bool, Int, Float64, Struct, Sliceโฆ |
| Extract to any | v.Interface() | Panics on unexported field |
| Number of fields | t.NumField() | Struct only |
| Field by index | t.Field(i) / v.Field(i) | Returns StructField / Value |
| Field by name | t.FieldByName("F") | Returns (StructField, bool) |
| Exported check | f.IsExported() | False for unexported fields |
| Read struct tag | f.Tag.Get("json") | Empty string if tag absent |
| Safe tag read | f.Tag.Lookup("json") | Returns (val, ok) |
| Addressable check | v.CanSet() | Requires pointer + Elem() |
| Dereference ptr | v.Elem() | Works on Ptr and Interface kinds |
| Set int field | v.SetInt(n) | Panics if not addressable |
| Set string field | v.SetString(s) | Same addressability requirement |
| Allocate new T | reflect.New(t) | Returns *T as reflect.Value |
| Slice length | v.Len() | Also works for Array, Map, String, Chan |
| Slice index | v.Index(i) | Returns element as reflect.Value |
| Map keys | v.MapKeys() | Returns []reflect.Value |
| Num in/out | t.NumIn() / t.NumOut() | Function types only |
| Call function | v.Call([]reflect.Value{โฆ}) | Returns []reflect.Value |
| Method by name | v.MethodByName("M") | Check IsValid() before Call |
| Deep equality | reflect.DeepEqual(a, b) | nil slice โ empty slice |
| Zero value check | v.IsZero() | True if value equals its zero value |
| Nil check | v.IsNil() | Chan, Func, Interface, Map, Ptr, Slice |