Skip to content

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, as json.Unmarshal does.
  • Test CanSet before Set, and IsValid before touching what FieldByName returned. Both are one word and turn a panic into a branch.
  • Leave unexported fields alone. The rule is the package boundary, not a reflect limitation; a field you need set from outside is a field that should be exported, or reachable through a method.
  • Check Type compatibility with AssignableTo or ConvertibleTo before Set, so a mismatch is an error message you wrote rather than reflect.Set: value of type … is not assignable.
  • Use SetMapIndex for maps and Append for 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.

setField: need a pointer to a struct, got main.Order
setField: main.Order has no field Weight
setField: main.Order.discount is not settable (unexported)
setField: cannot assign string to main.Order.Quantity (int)
<nil> <nil>
{Customer:Ada Lovelace Quantity:3 discount:0}

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 is std::any::Any ↗, which the Rust library's Static vs dynamic dispatch ↗ shows recovering a concrete type with downcast_ref — a checked assertion, after which the fields are set by ordinary code. Private fields stay private; there is no CanAddr true, CanSet false 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 convention setattr ignores.
  • Java's Field.set ↗ sets a field on an object, and a private field needs setAccessible(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.go at go1.25.5 ↗ — mustBeAssignable, mustBeExported and Value.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".