Reflection
Reflection lets code inspect values and types at runtime. The basic pattern is:
value:=reflect.ValueOf(x)
kind:=value.Kind()
typ:=value.Type()A reflect.Value represents the actual runtime value. A reflect.Type describes its type. Many reflection methods panic when called on the wrong Kind, so check Kind() first.
Core operations
| Goal | Code |
|---|---|
| Get runtime value | reflect.ValueOf(x) |
| Get runtime type | reflect.TypeOf(x) |
| Get broad category | v.Kind() |
| Get exact type | v.Type() |
Convert back to any | v.Interface() |
Check .Interface() is allowed | v.CanInterface() |
| Check value exists | v.IsValid() |
| Check nil | v.IsNil() |
| Dereference pointer/interface | v.Elem() |
v := reflect.ValueOf(x)
if !v.IsValid() {
return
}
if v.Kind() == reflect.Pointer {
if v.IsNil()
{
return
}
v=v.Elem()
}Type versus Kind
type Name string
value := reflect.ValueOf(Name("Ben"))
fmt.Println(value.Type()) // main.Name
fmt.Println(value.Kind()) // stringType() gives the exact declared type. Kind() gives its underlying category.
Reading values
| Kind | Reader |
|---|---|
String | v.String() |
| Signed integer | v.Int() |
| Unsigned integer | v.Uint() |
| Float | v.Float() |
| Boolean | v.Bool() |
| Generic value | v.Interface() |
switch v.Kind() {
case reflect.String:
fmt.Println(v.String())
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
fmt.Println(v.Int())
case reflect.Bool:
fmt.Println(v.Bool())
}Interface() panics for unexported struct fields. Check first:
if v.CanInterface() {
value := v.Interface()
}Pointers and interfaces
Pointers and interface wrappers usually need unwrapping:
func getValue(x any) reflect.Value {
v := reflect.ValueOf(x)
for v.IsValid() &&
(v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
return v
}Structs
| Goal | Code |
|---|---|
| Number of fields | v.NumField() |
| Field by position | v.Field(i) |
| Field by name | v.FieldByName("Name") |
| Field metadata | v.Type().Field(i) |
| Struct tag | field.Tag.Get("json") |
for i := 0; i < v.NumField(); i++ {
fieldValue := v.Field(i)
fieldType := v.Type().Field(i)
fmt.Println(fieldType.Name)
fmt.Println(fieldType.Type)
fmt.Println(fieldType.Tag.Get("json"))
}Only exported fields can safely be converted with Interface().
if fieldValue.CanInterface() {
fmt.Println(fieldValue.Interface())
}Slices and arrays
| Goal | Code |
|---|---|
| Length | v.Len() |
| Capacity | v.Cap() |
| Element | v.Index(i) |
for i := 0; i < v.Len(); i++ {
element := v.Index(i)
CallWalkIfPossible(element, fn)
}Maps
| Goal | Code |
|---|---|
| Get keys | v.MapKeys() |
| Get value | v.MapIndex(key) |
| Modern iterator | v.MapRange() |
iterator := v.MapRange()
for iterator.Next() {
key := iterator.Key()
value := iterator.Value()
fmt.Println(key, value)
}Map iteration order is unspecified.
Functions
| Goal | Code |
|---|---|
| Call function | v.Call(arguments) |
| Call with no arguments | v.Call(nil) |
| Number of parameters | v.Type().NumIn() |
| Number of results | v.Type().NumOut() |
results := v.Call(nil)
for _, result := range results {
CallWalkIfPossible(result, fn)
}Calling a function with the wrong arguments causes a panic.
if v.Type().NumIn() == 0 {
results := v.Call(nil)
}Channels
| Goal | Code |
|---|---|
| Blocking receive | v.Recv() |
| Non-blocking receive | v.TryRecv() |
| Send | v.Send(value) |
| Non-blocking send | v.TrySend(value) |
for {
value, open := v.Recv()
if !open {
break
}
CallWalkIfPossible(value, fn)
}This blocks until a value arrives or the channel closes.
A simpler option is:
for {
value, open := v.Recv()
if !open {
return
}
CallWalkIfPossible(value, fn)
}Changing values
A reflected value must be settable.
fmt.Println(v.CanSet())Usually, you must pass a pointer and then call Elem():
name := "Ben"
v := reflect.ValueOf(&name).Elem()
if v.CanSet() {
v.SetString("Benjamin")
}| Kind | Setter |
|---|---|
| String | v.SetString(...) |
| Integer | v.SetInt(...) |
| Boolean | v.SetBool(...) |
| Generic | v.Set(otherValue) |
Common panic checks
if !v.IsValid() {
return
}
switch v.Kind() {
case reflect.Pointer, reflect.Interface,
reflect.Map, reflect.Slice, reflect.Chan,
reflect.Func:
if v.IsNil() {
return
}
}
if v.CanInterface() {
value := v.Interface()
_ = value
}Common rules
| Before calling | Check |
|---|---|
Elem() | Pointer or interface, and not nil |
Interface() | CanInterface() |
Set() | CanSet() |
IsNil() | Nil-capable kind |
Field() | Struct |
Index() | Array, slice or string |
Call() | Func and correct arguments |
Recv() | Chan |