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 Types, 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.

golang