reflect sets only addressable values¶
Level: 301 · anyone whose SetInt panicked, or whose decoder has to fill a struct it was handed
One line: reflect.ValueOf(x) describes a copy, so CanSet is false and any Set panics; reflect.ValueOf(&x).Elem() is the variable itself and can be set — unless the field was reached through an unexported name, which stays readable and never settable, or the value sits in a map, where SetMapIndex is the only way in.
The program tries every way of changing a value through reflect on an Account: a plain int, the same int through a pointer, an unexported pin field, two exported fields set by name, a field that does not exist, a value of the wrong type, and a map element. Each refusal is a recovered panic, printed with its message. Then it builds values from nothing: reflect.New for a fresh Account, and MakeSlice plus Append for a []string whose type was only known as a reflect.Type.
Verified output of reflect_sets_only_addressable_values_go.go — regenerated by tools/run_examples.py, never hand-typed.
ValueOf(balance).CanSet(): false
ValueOf(&balance).Elem().CanSet(): true
after SetInt through the pointer: balance = 250
SetInt on the copy: reflect: reflect.Value.SetInt using unaddressable value
pin: CanAddr true, CanSet false, Int() 1234
SetInt on unexported: reflect: reflect.Value.SetInt using value obtained using unexported field
Interface on unexported: reflect.Value.Interface: cannot return value obtained from unexported field or method
after setting by name: {Owner:Ada Lovelace Balance:175 pin:1234}
FieldByName("Missing").IsValid(): false
Set a string into an int: reflect.Set: value of type string is not assignable to type int
MapIndex(...).CanSet(): false
after SetMapIndex: map[Ada:750]
reflect.New: *main.Account, Elem().CanSet() true
&{Owner:Grace Balance:0 pin:0}
MakeSlice+Append: [Ada Grace], len 2, cap 2, type []string
back as []string: [Ada Grace] 2
Reading the output¶
A copy cannot be set. reflect.ValueOf(balance) receives an interface value, and an interface value holds a copy of balance (An interface is two words). Setting the copy would change nothing the caller can see, so CanSet ↗ is false and SetInt panics with reflect: reflect.Value.SetInt using unaddressable value. This is the third law of reflection: a reflection object can modify a value only if it is settable (The Laws of Reflection ↗).
Through a pointer, Elem is the variable. reflect.ValueOf(&balance) holds a copy of the pointer, and Elem ↗ follows it to the variable itself: addressable, CanSet true, and SetInt(250) is visible in balance afterwards. Every decoder in the standard library asks for a pointer for exactly this reason, and go vet's unmarshal check flags a json.Unmarshal that forgets.
Unexported: CanAddr true, CanSet false. pin was reached through &acct, so it is addressable — the value is there and Int() reads 1234 — but it was obtained using an unexported field, and the Value docs ↗ make that the second condition for CanSet. SetInt panics with reflect: reflect.Value.SetInt using value obtained using unexported field, and Interface() refuses too, because handing the field out as an any would let a package read what its author hid. fmt prints pin:1234 all the same: it uses reflect and reads, which is allowed.
By name, checked at run time. FieldByName ↗ returns the field, or the zero Value when there is no such field — IsValid is the test, and a method on the zero Value would panic. Set ↗ checks assignability the way the compiler would have: a string into the int field panics with reflect.Set: value of type string is not assignable to type int. Nothing here is checked before the program runs; the compiler errors page lists the panics.
A map element is not addressable. MapIndex returns a copy (CanSet false), because a map may move its entries when it grows, so SetMapIndex ↗ writes the entry back under its key. The spec's address operators ↗ say the same for &m[k] in ordinary Go.
New, MakeSlice, Append: values from a Type. reflect.New(t) ↗ returns a Value holding a *Account, whose Elem is settable — the same shape as &acct. MakeSlice ↗ and Append ↗ grow a []string that was named only as a reflect.Type, and Interface().([]string) brings it back to a Go slice. This is how a decoder allocates the slice a JSON array fills.
reflect_sets_only_addressable_values_go.go
reflect_sets_only_addressable_values_go.go in full — pasted here by tools/run_examples.py from the file CI runs.
// A reflect.Value can change the variable it came from only when it is
// addressable and was not reached through an unexported field. ValueOf(x) is
// a copy and cannot be set; ValueOf(&x).Elem() is the variable itself.
//
// go build reflect_sets_only_addressable_values_go.go && ./reflect_sets_only_addressable_values_go
package main
import (
"fmt"
"reflect"
)
type Account struct {
Owner string
Balance int
pin int // unexported: readable through reflect, never settable
}
func main() {
balance := 100
fmt.Println("ValueOf(balance).CanSet(): ", reflect.ValueOf(balance).CanSet())
fmt.Println("ValueOf(&balance).Elem().CanSet():", reflect.ValueOf(&balance).Elem().CanSet())
reflect.ValueOf(&balance).Elem().SetInt(250)
fmt.Println("after SetInt through the pointer: balance =", balance)
fmt.Println("SetInt on the copy: ", panics(func() { reflect.ValueOf(balance).SetInt(1) }))
acct := Account{Owner: "Ada", Balance: 100, pin: 1234}
v := reflect.ValueOf(&acct).Elem()
pin := v.FieldByName("pin")
fmt.Printf("pin: CanAddr %t, CanSet %t, Int() %d\n", pin.CanAddr(), pin.CanSet(), pin.Int())
fmt.Println("SetInt on unexported: ", panics(func() { pin.SetInt(0) }))
fmt.Println("Interface on unexported: ", panics(func() { _ = pin.Interface() }))
v.FieldByName("Owner").SetString("Ada Lovelace")
v.FieldByName("Balance").SetInt(175)
fmt.Printf("after setting by name: %+v\n", acct)
fmt.Println("FieldByName(\"Missing\").IsValid():", v.FieldByName("Missing").IsValid())
fmt.Println("Set a string into an int:", panics(func() { v.FieldByName("Balance").Set(reflect.ValueOf("lots")) }))
// A map element is not addressable either; SetMapIndex is the way in.
limits := map[string]int{"Ada": 500}
fmt.Println("MapIndex(...).CanSet(): ", reflect.ValueOf(limits).MapIndex(reflect.ValueOf("Ada")).CanSet())
reflect.ValueOf(limits).SetMapIndex(reflect.ValueOf("Ada"), reflect.ValueOf(750))
fmt.Println("after SetMapIndex:", limits)
// reflect.New: a pointer to a fresh zero value, whose Elem is settable.
p := reflect.New(reflect.TypeOf(Account{}))
fmt.Printf("reflect.New: %s, Elem().CanSet() %t\n", p.Type(), p.Elem().CanSet())
p.Elem().FieldByName("Owner").SetString("Grace")
fmt.Printf(" %+v\n", p.Interface())
// MakeSlice and Append build a slice of a type known only at run time.
owners := reflect.MakeSlice(reflect.TypeOf([]string{}), 0, 2)
owners = reflect.Append(owners, reflect.ValueOf("Ada"), reflect.ValueOf("Grace"))
fmt.Printf("MakeSlice+Append: %v, len %d, cap %d, type %s\n", owners, owners.Len(), owners.Cap(), owners.Type())
names := owners.Interface().([]string)
fmt.Println(" back as []string:", names, len(names))
}
// panics runs f and reports what it panicked with, or "no panic".
func panics(f func()) (msg any) {
defer func() { msg = recover() }()
f()
return "no panic"
}
What to do¶
- Take a pointer when you intend to set.
reflect.ValueOf(&x).Elem()is the settable form;reflect.ValueOf(x)can only read. Return an error when a caller hands you a non-pointer, asjson.Unmarshaldoes. - Test
CanSetbeforeSet, andIsValidbefore touching whatFieldByNamereturned. Both are one word and turn a panic into a branch. - Leave unexported fields alone. The rule is the package boundary, not a
reflectlimitation; a field you need set from outside is a field that should be exported, or reachable through a method. - Check
Typecompatibility withAssignableToorConvertibleTobeforeSet, so a mismatch is an error message you wrote rather thanreflect.Set: value of type … is not assignable. - Use
SetMapIndexfor maps andAppendfor slices — the operations that ordinary Go also needs, since neither element is addressable.
Practice¶
Write setField(target any, name string, value any) error that sets the field name of the struct target points at to value, and returns an error — never a panic — when target is not a pointer to a struct, when the field does not exist, when it is unexported, and when value's type is not assignable to the field. Show all four errors on an Order and then two successful sets.
Solution
reflect_sets_only_addressable_values_kata_go.go in full — pasted here by tools/run_examples.py from the file CI runs.
// Kata: setField(target, name, value) sets a struct field by name through a
// pointer and returns an error, never a panic, for a target that is not a
// pointer to a struct, a field that does not exist, an unexported field, and
// a value of the wrong type.
//
// go build reflect_sets_only_addressable_values_kata_go.go && ./reflect_sets_only_addressable_values_kata_go
package main
import (
"fmt"
"reflect"
)
type Order struct {
Customer string
Quantity int
discount float64
}
func setField(target any, name string, value any) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
return fmt.Errorf("setField: need a pointer to a struct, got %s", v.Type())
}
f := v.Elem().FieldByName(name)
if !f.IsValid() {
return fmt.Errorf("setField: %s has no field %s", v.Elem().Type(), name)
}
if !f.CanSet() {
return fmt.Errorf("setField: %s.%s is not settable (unexported)", v.Elem().Type(), name)
}
nv := reflect.ValueOf(value)
if !nv.Type().AssignableTo(f.Type()) {
return fmt.Errorf("setField: cannot assign %s to %s.%s (%s)", nv.Type(), v.Elem().Type(), name, f.Type())
}
f.Set(nv)
return nil
}
func main() {
order := Order{Customer: "Ada", Quantity: 1}
fmt.Println(setField(order, "Quantity", 3))
fmt.Println(setField(&order, "Weight", 3))
fmt.Println(setField(&order, "discount", 0.1))
fmt.Println(setField(&order, "Quantity", "three"))
fmt.Println(setField(&order, "Quantity", 3), setField(&order, "Customer", "Ada Lovelace"))
fmt.Printf("%+v\n", order)
}
Verified output of reflect_sets_only_addressable_values_kata_go.go — regenerated by tools/run_examples.py, never hand-typed.
In other languages¶
- Rust has no
reflect.Value: there is no way to set a field by its name at run time, and the closest thing isstd::any::Any↗, which the Rust library's Static vs dynamic dispatch ↗ shows recovering a concrete type withdowncast_ref— a checked assertion, after which the fields are set by ordinary code. Private fields stay private; there is noCanAddrtrue,CanSetfalse middle state. - Python's
setattr↗ sets any attribute by name, on the object itself, with no copy and no notion of an unexported field; the leading underscore is a conventionsetattrignores. - Java's
Field.set↗ sets a field on an object, and a private field needssetAccessible(true)first, which the module system may refuse — Java's version of the unexported rule, switchable where Go's is not.
Sources¶
- The Laws of Reflection ↗ — the third law, settability, and why
ValueOf(x)cannot be set. reflect↗:Value.CanSet↗,Value.CanAddr↗,Value.Elem↗,Value.Set↗,Value.SetMapIndex↗,New↗,MakeSlice↗,Append↗.- The panic messages:
src/reflect/value.goat go1.25.5 ↗ —mustBeAssignable,mustBeExportedandValue.Set. - The Go specification, Address operators ↗ — what is addressable, and that a map element is not.
- Alan A. A. Donovan and Brian W. Kernighan, The Go Programming Language (Addison-Wesley, 2015), chapter 12, "Reflection" — "Setting Variables with reflect.Value" and "Example: Decoding S-Expressions".
- Jon Bodner, Learning Go, 2nd ed. (O'Reilly, 2024), chapter 16, "Here Be Dragons: Reflect, Unsafe, and Cgo" — "Make New Values" and "Use Reflection to Write a Data Marshaler".
- Adam Freeman, Pro Go (Apress, 2022), chapter 27, "Using Reflection" — "Setting a Value Using Reflection" and "Creating New Values"; chapter 28, "Using Reflection, Part 2" — "Setting Struct Field Values" and "Creating, Copying, and Appending Elements to Slices".