Reflection with reflect

Go · Advanced · 14 min

Reflection powers encoding/json, ORMs and validators; you need it to write such libraries and to understand them.

The reflect package lets a program examine values whose type isn't known at compile time. It starts from an interface value:

  • reflect.TypeOf(x) returns a reflect.Type: its Name(), Kind() (Struct, Slice, Ptr, Int…), and for structs NumField() and Field(i), which includes the field's name and tag (f.Tag.Get("json")).
  • reflect.ValueOf(x) returns a reflect.Value holding the data, with accessors like Int(), String(), Len(), Index(i), Field(i), MapRange().

Kind vs Type: a type UserID int has Type UserID but Kind Int. Generic reflection code switches on Kind.

To modify a value through reflection you need an addressable value: pass a pointer and call Elem(), as in reflect.ValueOf(&cfg).Elem(). v.CanSet() reports whether setting is allowed; unexported fields are readable but not settable. Calling SetInt on a non-settable value, or Int() on a non-integer Kind, panics — reflection trades compile-time checks for run-time panics.

The Laws of Reflection summarise it: reflection goes from interface value to reflection object and back (v.Interface()), and to modify a reflection object the value must be settable.

Costs: reflective code is slower (often 10–100× for field access), allocates, and is harder to read. Prefer interfaces, generics or code generation (go generate) when they fit, and keep reflection at the edges — serialisation, dependency injection, test helpers. Cache per-type information such as parsed tags, since inspecting a Type repeatedly is wasteful.

Example

package main

import (
	"fmt"
	"reflect"
)

type Config struct {
	Host string `env:"HOST"`
	Port int    `env:"PORT"`
}

func main() {
	cfg := Config{Host: "localhost"}
	v := reflect.ValueOf(&cfg).Elem() // addressable struct
	t := v.Type()

	for i := range t.NumField() {
		f := t.Field(i)
		fmt.Println(f.Name, f.Type.Kind(), f.Tag.Get("env"))
	}
	// Host string HOST
	// Port int PORT

	if p := v.FieldByName("Port"); p.CanSet() {
		p.SetInt(8080)
	}
	fmt.Println(cfg.Port) // 8080
}

Remember

  • TypeOf for type info (Kind, fields, tags), ValueOf for data
  • To set via reflection, start from a pointer and call Elem()
  • Reflection errors are run-time panics; keep it at the edges

Watch out for

  • Calling reflect.ValueOf(cfg) (not &cfg) and then trying to set a field — it panics because the value isn't addressable.

Check yourself

  1. `type Celsius float64; var c Celsius` — what is `reflect.TypeOf(c).Kind()`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/blog/laws-of-reflection

Reflection with reflect · Go · Topolearn