Go’s reflect package is very powerful and very useful, but I’ve always struggled to wrap my head around it. The package comes with examples, but I’ve always felt the need of a more practical walk-through that stitches everything together.
If you haven’t read it yet, the Laws of Reflection post is very useful. But let me propose a different first law of reflection: You Will Fuck It Up. You’re knee deep inside Go at runtime, rewriting part of the program that’s executing. Write tests. Lots of tests.
This post is a practical collection of information that I need to swap back into my working memory when I reach for reflect. It includes examples of the thing I most commonly want to do: fuck around and find out with structs.
Building blocks
The important things are the Type
interface, the Value
type and the Kind
type.
Type
The Type
returns the reflect.Type
of a value. There are two methods:
TypeOf
.TypeFor
.
TypeOf
will return a Type
that describes a Go type:
func Example_typeOf() {
i := int8(5)
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(&i))
fmt.Println(reflect.TypeOf(uint(10)))
t := time.Now().UTC()
fmt.Println(reflect.TypeOf(t))
fmt.Println(reflect.TypeOf(&t))
fmt.Println(reflect.TypeOf(struct{}{}))
fmt.Println(reflect.TypeOf(&struct{}{}))
fmt.Println(reflect.TypeOf([]string{}))
fmt.Println(reflect.TypeOf(map[string]string{}))
// Output:
// int8
// *int8
// uint
// time.Time
// *time.Time
// struct {}
// *struct {}
// []string
// map[string]string
}
This looks like a “native” or “real” Go type because of how fmt.Println
renders it. But it’s not. When comparing two Type
s, do not compare them through their String
methods. Compare the two types directly.
Kind
The Kind
is a broad strokes categorisation of a Type
. It’s a method on a reflect.Type
and reflect.Value
.
func Example_kind() {
i := int8(5)
fmt.Println(reflect.TypeOf(i).Kind())
fmt.Println(reflect.TypeOf(&i).Kind())
fmt.Println(reflect.TypeOf(uint(10)).Kind())
t := time.Now().UTC()
fmt.Println(reflect.TypeOf(t).Kind())
fmt.Println(reflect.TypeOf(&t).Kind())
fmt.Println(reflect.TypeOf(struct{}{}).Kind())
fmt.Println(reflect.TypeOf(&struct{}{}).Kind())
fmt.Println(reflect.TypeOf([]string{}).Kind())
fmt.Println(reflect.TypeOf(map[string]string{}).Kind())
// Output:
// int8
// ptr
// uint
// struct
// ptr
// struct
// ptr
// slice
// map
}
As you can see, anything that’s a pointer is a reflect.Pointer
. Any struct is a reflect.Struct
, maps are reflect.Map
, slice reflect.Slice
etc. For any “complex” type, the Kind
represents the most outer layer of the type onion, so to speak.
Value
The Value
is an opaque type giving you access to the underlying concrete type. Not all methods on Value
work for everything underlying type.
func Example_value() {
i := int(10)
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(i).Kind())
val := reflect.ValueOf(i)
fmt.Println(val.Kind())
fmt.Println(val.CanSet())
// Output:
// int
// int
// int
// false
}
CanSet
returns false
here because we passed an int
instead of a *int
into reflect.ValueOf
. It needs to be a pointer to be mutable.
func Example_valueModify() { i := int(10) fmt.Println(reflect.TypeOf(i)) fmt.Println(reflect.TypeOf(i).Kind())
val := reflect.ValueOf(&i)
fmt.Println(val.Kind())
fmt.Println(val.CanSet())
elem := val.Elem()
fmt.Println(elem.Kind())
fmt.Println(elem.CanSet())
fmt.Println(elem.CanInt())
elem.SetInt(100)
fmt.Println(i)
// Output:
// int
// int
// ptr
// false
// int
// true
// true
// 100
}
When doing so, `ValueOf` will give us a `reflect.Pointer` and we'll need to call `Elem` on it to get the underlying `Value`. You can also `reflect.Indirect` a `Value`. If the passed `Value` is a pointer, it gets the underlying value. If it's nil, you'll get a zero `Value`. Otherwise it'll return the passed in `Value`.
## Struct walking
My main reason for using reflect is to walk a struct's fields, either to encode it to some format or populate a struct based on some payload.
In both of those cases, you need to always check for pointer types and nilability. When walking to encode, you want to either get a concrete type or get the `Elem` of the pointer type. When walking to decode, you want to ensure you've been given a pointer as otherwise you won't be able to populate the struct. It must also not be nil.
The other useful thing is to get the struct tags, so you can be provided with additional hints for how to encode or decode a field.
### Reading
Lets start with reading, as that's the bulk of it. Writing ends up being the same routine, but you then also modify the struct.
```go
func Example_read() {
walk := func(value any) {
if value == nil {
fmt.Println("value is nil")
return
}
typ := reflect.TypeOf(value)
val := reflect.ValueOf(value)
fmt.Println("type kind:", typ.Kind())
fmt.Println("value kind:", val.Kind())
if typ.Kind() == reflect.Pointer {
fmt.Println("value is a pointer")
if val.IsNil() {
fmt.Println("value is a nil pointer")
return
}
val = val.Elem()
typ = val.Type()
}
if typ.Kind() != reflect.Struct {
fmt.Println("value is not a struct")
return
}
for i := range val.NumField() {
field := typ.Field(i)
fieldValue := val.Field(i)
fmt.Println("field:", field.Name)
fmt.Println("field type:", field.Type)
fmt.Println("field kind:", field.Type.Kind())
fmt.Println("field value:", fieldValue.Interface())
fmt.Println("field value type:", fieldValue.Type())
fmt.Println("field value kind:", fieldValue.Type().Kind())
if fieldValue.Type().Kind() == reflect.Pointer {
fmt.Println("field value is a pointer")
if !fieldValue.IsNil() {
fmt.Println("field value pointer value:", fieldValue.Elem().Interface())
fmt.Println("field value pointer value type:", fieldValue.Elem().Type())
fmt.Println("field value pointer value kind:", fieldValue.Elem().Type().Kind())
} else {
fmt.Println("field value pointer is nil")
}
}
fmt.Println("field tag:", field.Tag.Get("data"))
}
}
type dummy struct {
Field1 int `data:"one,test"`
Field2 *time.Time `json:"-"`
}
data1 := dummy{
Field1: 100,
}
t := time.Unix(0, 0)
data2 := dummy{
Field1: 200,
Field2: &t,
}
fmt.Println("# ---- data1 ----# ")
walk(data1)
fmt.Println("\n# --- &data1 ---# ")
walk(&data1)
fmt.Println("\n# ---- data2 ----# ")
walk(data2)
fmt.Println("\n# - dummy(nil) - # ")
walk((*dummy)(nil))
fmt.Println("\n# ----- nil -----# ")
walk(nil)
// Output:
// # ---- data1 ----#
// type kind: struct
// value kind: struct
// field: Field1
// field type: int
// field kind: int
// field value: 100
// field value type: int
// field value kind: int
// field tag: one,test
// field: Field2
// field type: *time.Time
// field kind: ptr
// field value: <nil>
// field value type: *time.Time
// field value kind: ptr
// field value is a pointer
// field value pointer is nil
// field tag:
//
// # --- &data1 ---#
// type kind: ptr
// value kind: ptr
// value is a pointer
// field: Field1
// field type: int
// field kind: int
// field value: 100
// field value type: int
// field value kind: int
// field tag: one,test
// field: Field2
// field type: *time.Time
// field kind: ptr
// field value: <nil>
// field value type: *time.Time
// field value kind: ptr
// field value is a pointer
// field value pointer is nil
// field tag:
//
// # ---- data2 ----#
// type kind: struct
// value kind: struct
// field: Field1
// field type: int
// field kind: int
// field value: 200
// field value type: int
// field value kind: int
// field tag: one,test
// field: Field2
// field type: *time.Time
// field kind: ptr
// field value: 1970-01-01 01:00:00 +0100 CET
// field value type: *time.Time
// field value kind: ptr
// field value is a pointer
// field value pointer value: 1970-01-01 01:00:00 +0100 CET
// field value pointer value type: time.Time
// field value pointer value kind: struct
// field tag:
//
// # - dummy(nil) - #
// type kind: ptr
// value kind: ptr
// value is a pointer
// value is a nil pointer
//
// # ----- nil -----#
// value is nil
}
Writing
Writing isn’t much more than reading. Once you have a Value
, you call one of the Set
methods on it to set it. Instead of replicating the whole program, we’ll just look at how to Set
some things.
func Example_write() {
{
// Setting a value on a type
fmt.Println("# -- Setting value -- #")
i := int(8)
fmt.Println(i)
reflect.ValueOf(&i).Elem().SetInt(10)
fmt.Println(i)
}
{
fmt.Println("\n# -- Setting value on ptr -- #")
i := (*int)(nil)
fmt.Println(i)
// Get the Elem, not the pointer itself
val := reflect.Indirect(reflect.ValueOf(&i))
fmt.Println(val.IsNil())
// Since it's nil, allocate it
value := reflect.New(val.Type().Elem())
value.Elem().SetInt(10)
val.Set(value)
fmt.Println(*i)
}
{
fmt.Println("\n# -- Setting value on generic type -- #")
type Null[T comparable] struct{ Value T }
n := Null[string]{Value: "test"}
fmt.Println(n.Value)
// Change the value
val := reflect.Indirect(reflect.ValueOf(&n))
field := val.FieldByName("Value")
field.SetString("changed")
fmt.Println(n.Value)
// You can't change the T
// field.SetInt(8) <-- panic.
}
{
fmt.Println("\n# -- Setting a slice element -- #")
s := []string{"a", "b", "c"}
fmt.Println(s)
v := reflect.Indirect(reflect.ValueOf(&s))
// Modify an existing element
elem := v.Index(1)
elem.SetString("d")
fmt.Println(s)
// Append a new element
newElem := reflect.ValueOf("e")
v.Set(reflect.Append(v, newElem))
fmt.Println(s)
}
{
fmt.Println("\n# -- Setting a map key/value pair -- #")
m := map[string]int{
"foo": 1,
"bar": 2,
}
mk := slices.Collect(maps.Keys(m))
slices.Sort(mk)
for _, k := range mk {
fmt.Printf("%s: %d\n", k, m[k])
}
val := reflect.ValueOf(m) // don't need to &m here
// Update existing key
val.SetMapIndex(reflect.ValueOf("foo"), reflect.ValueOf(10))
// Delete existing key
val.SetMapIndex(reflect.ValueOf("bar"), reflect.Value{})
// Add baz
val.SetMapIndex(reflect.ValueOf("baz"), reflect.ValueOf(42))
mk = slices.Collect(maps.Keys(m))
slices.Sort(mk)
for _, k := range mk {
fmt.Printf("%s: %d\n", k, m[k])
}
}
// Output:
// # -- Setting value -- #
// 8
// 10
//
// # -- Setting value on ptr -- #
// <nil>
// true
// 10
//
// # -- Setting value on generic type -- #
// test
// changed
//
// # -- Setting a slice element -- #
// [a b c]
// [a d c]
// [a d c e]
//
// # -- Setting a map key/value pair -- #
// bar: 2
// foo: 1
// baz: 42
// foo: 10
}
Interface comparison
Though you can usually use type assertions for checking if some type implements an interface, if you’ve got a reflect.Type
there’s a way to check that too.
func Example_interface() {
ioWriter := reflect.TypeOf((*io.Writer)(nil)).Elem()
fileType := reflect.TypeOf(os.Stdout)
fmt.Println(fileType.Implements(ioWriter))
// Output:
// true
}
Recent additions
The reflect package has added very useful helpers over time. Equal
is a thing, Grow
exists to grow slices, SetZero
to set a field to its zero value, Clear
for maps, Comparable
to check if a Value
is comparable etc.
You can also dynamically create structs, using StructFields
.
Conclusion
To spare yourself the headache of checking and getting the Value
of reflect.Pointer
, use reflect.Indirect
. It will keep your code more readable. But no matter what you do, your code will quickly start to read like type kind value elem value soup. A switch over reflect.Kind
can be very helpful to organise things and call smaller functions.
When using reflect, you have to practice defensive programming. Calling the wrong method for a Type
or Value
is guaranteed a panic. There are limits on what you can Set
. Check everything. Everything. Use Can
methods to see if you can modify things, and Is
methods to ensure you got the Value
you expected. Is
can panic too, so check the documentation.