Reflection

reflect package - reflect - Go Packages
Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types.
https://pkg.go.dev/reflect#Value

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

GoalCode
Get runtime valuereflect.ValueOf(x)
Get runtime typereflect.TypeOf(x)
Get broad categoryv.Kind()
Get exact typev.Type()
Convert back to anyv.Interface()
Check .Interface() is allowedv.CanInterface()
Check value existsv.IsValid()
Check nilv.IsNil()
Dereference pointer/interfacev.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()) // string

Type() gives the exact declared type. Kind() gives its underlying category.


Reading values

KindReader
Stringv.String()
Signed integerv.Int()
Unsigned integerv.Uint()
Floatv.Float()
Booleanv.Bool()
Generic valuev.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

GoalCode
Number of fieldsv.NumField()
Field by positionv.Field(i)
Field by namev.FieldByName("Name")
Field metadatav.Type().Field(i)
Struct tagfield.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

GoalCode
Lengthv.Len()
Capacityv.Cap()
Elementv.Index(i)
for i := 0; i < v.Len(); i++ {
	element := v.Index(i)
	CallWalkIfPossible(element, fn)
}

Maps

GoalCode
Get keysv.MapKeys()
Get valuev.MapIndex(key)
Modern iteratorv.MapRange()
iterator := v.MapRange()

for iterator.Next() {
	key := iterator.Key()
	value := iterator.Value()

	fmt.Println(key, value)
}

Map iteration order is unspecified.


Functions

GoalCode
Call functionv.Call(arguments)
Call with no argumentsv.Call(nil)
Number of parametersv.Type().NumIn()
Number of resultsv.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

GoalCode
Blocking receivev.Recv()
Non-blocking receivev.TryRecv()
Sendv.Send(value)
Non-blocking sendv.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")
}
KindSetter
Stringv.SetString(...)
Integerv.SetInt(...)
Booleanv.SetBool(...)
Genericv.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 callingCheck
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