The reflect package gives you two handles on any Go value: a reflect.Type that describes what the value is, and a reflect.Value that wraps the value itself. Almost every reflection program is some combination of producing these two things, asking them questions, and either reading or writing through them. This chapter covers how reflect.TypeOf and reflect.ValueOf work, how Kind differs from Type, how to read primitive and container values, and the rules that decide whether you can write back through a Value.
Every interface value carries a type descriptor and a pointer to the data. The reflect package exposes those two halves through reflect.TypeOf and reflect.ValueOf. Both take an interface{} (also written as any), so anything you can pass to fmt.Println you can pass to either of them.
TypeOf returns a reflect.Type, which is an interface. The concrete value behind it is the runtime's internal type descriptor (*reflect.rtype), but you should treat it as an opaque interface and only use the methods reflect.Type defines. ValueOf returns a reflect.Value, which is a struct that wraps the original value plus its type and a few flag bits.
The pattern is a round trip: you start with a Go value, put it into an interface (which TypeOf and ValueOf do automatically), and get back two handles describing it.
The diagram shows the loop. A concrete value becomes an interface when it crosses into TypeOf or ValueOf. From the interface, the reflect package extracts a Type and a Value. The Value can be turned back into an interface with Interface(), which closes the loop. Everything reflection does, reading fields, modifying through pointers, calling methods, happens on the right side of that diagram.
Cost: Both TypeOf and ValueOf allocate on the heap when their argument isn't already an interface, because boxing a value into an interface usually copies it to the heap. Call them outside hot loops where you can.
reflect.Type answers "what is this type called". Kind answers "what category of type is it underneath". Two distinct types can share the same kind.
Three different Type values, one Kind. Kind is the underlying machine-level shape of the type, the same dozen-ish categories Go's runtime cares about (Int, String, Slice, Map, Struct, Ptr, Interface, Func, Chan, and a few others). Type is the named declaration, including the package path.
This matters because reflection code that wants to read an integer field doesn't care whether the field is declared as int, ProductID, or OrderID. It cares that the field's Kind is Int. The reading methods we'll see next, Int(), Float(), String(), all dispatch on Kind, not on Type.
The full list of kinds covers every Go type category:
| Kind group | Members |
|---|---|
| Numbers | Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Float32, Float64, Complex64, Complex128 |
| Text | String |
| Boolean | Bool |
| Containers | Array, Slice, Map, Chan |
| Composite | Struct, Interface |
| Function | Func |
| Pointer-like | Ptr, UnsafePointer |
| Absent | Invalid |
Invalid is what you get when a reflect.Value doesn't wrap anything, typically from reflect.ValueOf(nil) or from a zero reflect.Value{}. Code that consumes arbitrary input usually starts with if v.Kind() == reflect.Invalid { return } to guard against that.
A reflect.Value whose kind is one of the numeric, string, or boolean kinds has typed accessors that pull out the underlying value. The accessor names match the kind groups, not the specific kind, so Int() returns int64 for any signed integer kind, and Float() returns float64 for any float kind.
A few details to note. v.Int() returns int64 even when the underlying value is an int32. The reflect package upcasts because returning a kind-specific type would force one accessor per integer kind, which would be ugly to use. Second, v.String() only returns the underlying string when the kind is String. Call it on any other kind and you get a placeholder like <int Value>. That's intentional, the method doesn't panic so it's safe to call from a fmt formatter, but it also doesn't do what you'd hope. Third, calling the wrong accessor for the actual kind panics. v.Int() on a reflect.Value of kind String panics with reflect: call of reflect.Value.Int on string Value.
The right pattern is what the example shows: branch on Kind first, then call the accessor that matches that branch. Skipping the kind check and hoping is the fastest way to a panic in production.
Cost: The accessors themselves are cheap, but the interface{} boxing that gets the value into reflection in the first place isn't. If you're reading thousands of values, batch them: hold one reflect.Value and walk its contents, rather than calling reflect.ValueOf per item.
Slices, arrays, and maps each have their own set of methods. The pattern is the same as primitives: check the kind, then call methods that work for that kind.
Len() works for slices, arrays, maps, strings, and channels. Index(i) works for slices, arrays, and strings (returning the i-th byte for strings). Both methods return a reflect.Value for one element, which you can then read with the kind-appropriate accessor.
Maps need two methods: MapKeys() for the list of keys and MapIndex(key) for the value at a key. Both keys and values come back as reflect.Value.
MapKeys returns a []reflect.Value with one entry per key, in unspecified order (same as iterating with range on the map). MapIndex returns the zero reflect.Value{} when the key is missing, which you can check with v.IsValid(). Don't compare against nil, a reflect.Value is a struct and never literally nil.
Cost: MapKeys allocates a slice. For one-shot inspection that's fine, but iterating millions of times that way is wasteful. Go 1.18+ added Value.MapRange() which returns an iterator and avoids the allocation.
Interface() is the inverse of ValueOf. It returns the wrapped value as an interface{}, which you can then type-assert back to a concrete type or pass to anything that accepts any.
This closes the loop the diagram earlier showed. You start with Order, go through reflect.Value, and come back with the same Order boxed in an interface, which a type assertion peels open. Most reflection libraries use this pattern at their edges: take the input from the caller, do the reflective work, then hand a concrete value back through Interface() so the caller never has to know reflection was involved.
One detail to know. Interface() panics if the Value was obtained from an unexported struct field, because exposing those would break encapsulation. Reading the value of an unexported field is still possible with the Kind-based accessors (Int(), String(), etc.) but boxing it back into an interface is not.
When the value wrapped by a reflect.Value is itself a pointer, calling kind-based accessors on it doesn't reach through to the pointed-at value. Elem() is the operation that walks one level of indirection.
v holds the pointer, elem holds what the pointer points at. Elem() only works on Ptr and Interface kinds. Call it on, say, a Struct and it panics with reflect: call of reflect.Value.Elem on struct Value.
Elem() is also how you reach through an interface. If a reflect.Value has kind Interface, Elem() returns the value stored inside the interface, with the dynamic type's kind. This matters when you reflect over a slice of any:
Each slot in items has static kind Interface. Elem() unwraps it to expose the dynamic value, whose kind is the actual stored type. Without Elem(), the loop would only ever see Kind() == Interface and couldn't dispatch on the real types.
Reading through a reflect.Value is straightforward. Writing is governed by two related properties: addressability and settability. The rule, briefly:
The reflect package exposes both with CanAddr() and CanSet(). Settability implies addressability, so CanSet() is the one you usually check before writing.
The catch: reflect.ValueOf(x) always gives you a non-addressable, non-settable value, even if x is a local variable that's perfectly addressable in Go terms. The reason is that ValueOf takes its argument as interface{}, and putting a value into an interface copies it. The reflect.Value wraps the copy, not the original. Modifying the copy would be meaningless, so the reflect package refuses to let you do it at all.
To get a settable reflect.Value for price, you have to pass a pointer and call Elem() to walk through it:
The pointer &price is itself copied into the interface (pointers are cheap to copy), but the pointer still points at the original price. Elem() reaches through the pointer and lands on the real variable. From there, SetFloat(9.99) writes through to the variable, and the program sees the update.
To modify a value through reflection, take its address first and call Elem(). Without that, you're working on a copy and your changes vanish.
Cost: The pointer-and-Elem pattern doesn't add measurable overhead beyond the pointer indirection. The real cost is the dynamic type check inside SetFloat, which the runtime must do every call. In hot paths where the same field type is being set many times, generating code (or using unsafe) is faster, but for ordinary use this is fine.
The diagram contrasts the two paths. Pass the value directly and you land in the "writes go nowhere" branch. Pass a pointer, call Elem(), and you land in the branch where Set* actually updates the original variable. There's no third option for modifying a local through reflection.
The same idea applies to struct fields, which is the more common case. If o is an Order value, reflect.ValueOf(o).Field(0) gives you a non-settable wrapper around ID. But reflect.ValueOf(&o).Elem().Field(0) gives you a settable one, because the chain is "pointer to o, dereference to land on o itself, then field".
A reflection routine that walks an unknown value tends to use most of what this chapter covered in sequence: get the Type and Value, branch on Kind, read with the kind-appropriate accessor, and optionally dereference with Elem() when the value is a pointer.
describe doesn't know what type it's getting. It uses Kind() to dispatch, calls the matching accessor, and falls through cleanly for kinds it doesn't handle. The pointer case at the bottom shows the Elem() step: the original argument was *Product, but after the if block the wrapper is a Product and the rest of the function treats it like any other struct.
This is the shape almost every reflection helper takes. JSON encoders, ORMs, validators, and config loaders all start with some variation of "get a reflect.Value, walk through pointers, switch on kind, read or write".
10 quizzes