skip to content

Settability and Addressability

reflect can only write through a value it reached via a pointer, and never into an unexported field. Every reflect.Value.Set panic anyone has ever debugged comes from one of those two rules.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

Why does writing a field via reflect.ValueOf(cfg) panic when reflect.ValueOf(&cfg).Elem() works?

level: juniorimportance: must knowfreq 55%

answer

  1. reflect only sees what you handed it
  2. an interface parameter takes a copy
  3. a copy has no address to write through
  4. pass the pointer, then call Elem
  5. CanAddr false forces CanSet false

basics

~20 s

reflect.ValueOf copies its argument into an interface, so the struct it holds has no address and every Set call panics. Passing &cfg and then calling Elem aims reflect at the original variable, which is addressable and therefore settable.

solid answer

~40 s

`reflect.ValueOf` takes an `any`, so a struct argument is copied into that interface before reflect ever sees it. Writing through the copy could not possibly affect the caller's variable, so reflect refuses: the resulting Value is flagged unaddressable, `CanAddr` and `CanSet` both report false, and `SetInt` or `SetString` panic with a message about using an unaddressable value. When you pass `&cfg` instead, the pointer is copied but the pointee is not; `Value.Elem()` follows that pointer to the original storage, so the Value it returns is addressable and its exported fields report `CanSet() == true`. It is the runtime version of a rule the compiler already enforces on unaddressable expressions, and it is why every standard-library decoder that fills a struct insists on a non-nil pointer.

code

go · 14 lines
go
type Config struct {
	Port int
}

cfg := Config{Port: 8080}

byValue := reflect.ValueOf(cfg).Field(0)
fmt.Println(byValue.CanAddr(), byValue.CanSet()) // false false

byPointer := reflect.ValueOf(&cfg).Elem().Field(0)
fmt.Println(byPointer.CanAddr(), byPointer.CanSet()) // true true

byPointer.SetInt(9090)
fmt.Println(cfg.Port) // prints: 9090

go deeper

for a junior

Recall the mechanical rule: hand reflect a pointer and call Elem before you try to write. Be ready to say that reflect.ValueOf copies its argument, so a Value made from a plain struct cannot be set.

for a middle

Explain the mechanism: the any parameter copies the struct, the Value carries an addressable flag, and that flag propagates to fields. Know the slice-versus-array asymmetry and why map entries are never addressable.

for a senior

Show you would validate the pointer at the API boundary and return an error, the way stdlib decoders do, instead of letting a panic surface deep inside a walk. Be ready to describe a CanAddr/CanSet probe as your first diagnostic.

for a principal

Frame it as an API contract: any function that fills a caller's value must document and enforce the pointer requirement, because the failure mode of getting it wrong is silence, not a compile error.

## The one-sentence rule A `reflect.Value` can be written only if reflect knows the **address** of the storage behind it. `Value.CanSet` reports exactly that (plus an export check covered separately). If reflect only holds a copy, a write would change nothing observable, so instead of silently doing nothing, reflect panics. ## Why `reflect.ValueOf(cfg)` is a copy `reflect.ValueOf` has the signature `func ValueOf(i any) Value`. The parameter is an interface, so calling it with a struct value performs an ordinary Go conversion: the struct is copied into the interface. From that moment the original variable `cfg` and the data inside the interface are two separate pieces of memory. Reflect records, in a flag on the Value, that this data is not addressable. Everything you reach *through* that Value inherits the problem. `Field(0)` on an unaddressable struct gives an unaddressable field; so does `FieldByName("Port")`. Reading works fine — `Kind`, `Int`, `String`, `Interface` all behave — but any mutator panics: - `CanAddr() == false` - `CanSet() == false` - `SetInt(9090)` panics with `reflect: reflect.Value.SetInt using unaddressable value` - `Addr()` panics too, because there is no address to hand out ## Why the pointer detour works `reflect.ValueOf(&cfg)` also copies its argument — but the argument is a pointer, and copying a pointer copies the address, not the struct. The Value now has `Kind() == reflect.Pointer`. `Value.Elem()` on a pointer Value dereferences it and returns a Value describing **the original storage**, with the addressable flag set. So: ``` v := reflect.ValueOf(&cfg).Elem() // Kind Struct, CanAddr true f := v.Field(0) // inherits addressability f.SetInt(9090) // cfg.Port is now 9090 ``` Addressability propagates downward: a field of an addressable struct is addressable, an element of an addressable array is addressable, and so on. ## The parts people get wrong **"But `cfg` is a variable, and variables are addressable."** True in the source code — and irrelevant. Addressability is a property of the *Value reflect holds*, and reflect was handed an interface copy. The language's addressability of `cfg` never travelled through the `any` parameter. **Slices are the confusing exception.** `reflect.ValueOf(s).Index(0)` on a slice `s` *is* settable. Only the three-word slice header was copied; it still points at the same backing array, so reflect can compute the element's address. An array is the opposite: `reflect.ValueOf(arr).Index(0)` is unaddressable, because the entire array was copied into the interface. Map entries are unaddressable in every case — reflect can rehash and move them — which is why writing to a map goes through `Value.SetMapIndex` rather than through a settable element Value. **`Elem` is not a general "unwrap".** `Value.Elem` panics unless the Kind is `Pointer` or `Interface`. Calling it on a struct Value is a common beginner panic. **A nil pointer is not enough.** `reflect.ValueOf((*Config)(nil)).Elem()` returns the zero Value: `IsValid()` is false, `CanSet()` is false, and any write panics. A loader should check `v.Kind() == reflect.Pointer && !v.IsNil()` before dereferencing. ## Why this matters in real code Anything that fills a struct from outside data — a decoder, an environment-variable loader, a test fixture builder — has to take a pointer for this reason. That is why `encoding/json.Unmarshal` returns an error when it is handed a non-pointer instead of quietly succeeding: without a pointer it has nowhere to write. When you write the same kind of loader yourself, validate at the entry point and return a clear error, so a caller who passes the struct by value learns about it immediately rather than through settings that mysteriously stay at their defaults. ## A probe worth remembering When a reflect walk is not writing, print `CanAddr()` and `CanSet()` for the top-level Value and for one field. Two falses at the top means the walk started from a copy; the fix is one `&` and one `Elem()` at the entry point, not anywhere deeper in the walk.

  • If reflect.ValueOf copies its argument, why is reflect.ValueOf(s).Index(0) settable for a slice s?
    Because only the slice header is copied, and it still points at the same backing array, so reflect can compute the element's address: `CanSet` is true and the write is visible through `s`. An array behaves the opposite way — `reflect.ValueOf(arr).Index(0)` is unaddressable, since the whole array was copied into the interface.
  • Does Value.Elem on a nil pointer give you something settable?
    No. `Elem` on a nil pointer returns the zero Value: its `Kind` is `Invalid`, `IsValid` and `CanSet` are false, and any `Set` call panics. A loader should test `v.Kind() == reflect.Pointer && !v.IsNil()` at its entry point and return an error, rather than panicking somewhere deep in the walk.
  • Can you write to a map entry through a settable reflect.Value?
    No. Map entries are never addressable, because the runtime may move them when the map grows, so `MapIndex` returns an unaddressable Value. Mutating a map through reflect goes through `Value.SetMapIndex(key, val)` on the map Value itself, which must be non-nil; the map Value does not need to be addressable for that.

Handing reflect a struct is like giving someone a photocopy of a form: they can read every box, but filling one in changes nothing for you. Giving them the address of the original is what lets them write.

saying these in an interview costs you the question

  • Claims reflect can write through any Value it holds
  • Says a local variable is settable because it is addressable in source
  • Calls Elem on a struct Value and expects the struct back
  • Thinks CanSet only checks whether the field is exported
  • Passes the struct by value and concludes reflect writes are unreliable
open as a page

Why does reflect.Value.CanSet return false for an addressable but unexported struct field?

level: middleimportance: should knowfreq 40%

basics

~20 s

Because reflect refuses to let outside code break a package's encapsulation. A Value reached through an unexported field is flagged read-only: CanAddr stays true, CanSet is false, and both Set and Interface on it panic.

open as a page

A reflect-based config loader leaves every setting at its default — how do you diagnose it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Print CanAddr and CanSet for each Value the walk touches. Both false at the top level means the loader was handed a struct copy instead of a pointer, and its defensive skip-if-not-settable guard turned that into silence. Validate the pointer at the entry point.

open as a page

Why is reflect.Value.Elem settable for a pointer but not for a value held in an interface?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

Elem on a pointer returns the storage the pointer names, so it is addressable and settable. Elem on an interface returns a copy of the dynamic value the interface currently holds, which has no address of its own, so CanSet is false.

open as a page