32.5 Setting Values via Reflection
Alright, let’s get our hands dirty. You’ve inspected a value, found a field or an element you want to change, and now you want to actually change it. This is where we move from passive observation to active intervention, and where things get… interesting. The reflect package gives us reflect.Value.Set(), and it looks deceptively simple. The catch? It’s one of the most pedantic, fussy functions in the entire standard library. It will refuse to work for the slightest reason, and its error messages are about as helpful as a screen door on a submarine.
Let’s start with the cardinal rule, the one you must internalize or you’ll spend hours banging your head on your desk:
You can only Set a reflect.Value that you can Set.
I know, it sounds stupidly obvious. But it’s not. A reflect.Value you get by examining an existing variable (e.g., reflect.ValueOf(myVar)) is almost always not settable. It’s a copy of the value. Setting it would be like trying to change the original document by writing on a photocopy. The reflect package protects you from this nonsense by panicking.
To get a settable reflect.Value, you must start with a pointer and then traverse to the value that pointer points to. Let’s see it in action.
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
PrivateField string // We're going to change this, even though it's unexported. Watch.
PublicField int
}
func main() {
myInstance := MyStruct{PrivateField: "do not touch", PublicField: 42}
// Step 1: Get a pointer to the struct, as a `reflect.Value`
v := reflect.ValueOf(&myInstance) // v is now a Value of type *MyStruct
// Step 2: We need the actual struct, not the pointer.
// We use Elem() to dereference the pointer. THIS is the settable Value.
e := v.Elem() // e is now a Value of type MyStruct, and it is settable.
// Step 3: Find the field we want to change.
fieldToChange := e.FieldByName("PublicField")
// Step 4: Check if it's settable, because we're responsible adults.
fmt.Println("Is fieldToChange settable?", fieldToChange.CanSet()) // Prints: true
// Step 5: Set it to a new value of the same type.
if fieldToChange.CanSet() {
newValue := reflect.ValueOf(100) // Create a Value holding the int 100
fieldToChange.Set(newValue) // Do the deed
}
fmt.Printf("Updated struct: %+v\n", myInstance) // Prints: {PrivateField:do not touch PublicField:100}
}
See that? We started with &myInstance (a pointer), used .Elem() to get the underlying struct, and then we could set its field. If we had tried reflect.ValueOf(myInstance) directly, CanSet() would return false and Set() would panic.
The Type Mismatch Panic
Set is violently strict about types. You can’t set an int field with an int32 value. You can’t set a string with a []byte. The types must be identical. This is the most common cause of panics.
func main() {
x := 10
v := reflect.ValueOf(&x).Elem()
// This will panic: panic: reflect.Set: value of type int32 is not assignable to type int
newValue := reflect.ValueOf(int32(20))
v.Set(newValue)
}
The error message is actually pretty good here. Heed it. You must convert your source value to the exact expected type before creating the reflect.Value for Set.
Modifying Unexported Fields (The “Naughty” Way)
Here’s a fun party trick, and a perfect example of the reflect package’s weird moral flexibility. Normally, you can’t set an unexported (lowercase) field from outside its package. It’s a fundamental rule of Go. Reflection, however, gives you the tools to break this rule. It’s like the language is handing you a crowbar and looking the other way.
The CanSet method checks if a value is addressable and if the field is exported. So for an unexported field, CanSet() will be false. But CanAddr() might still be true. To modify it, we have to bypass the safety checks using reflect.NewAt and the unsafe package. This is, as the name implies, unsafe. But it works.
func main() {
myInstance := MyStruct{PrivateField: "do not touch", PublicField: 42}
v := reflect.ValueOf(&myInstance).Elem()
unexportedField := v.FieldByName("PrivateField")
fmt.Println("CanSet:", unexportedField.CanSet()) // false
fmt.Println("CanAddr:", unexportedField.CanAddr()) // true
// We can't use Set, but we can use unsafe to create a pointer to this field.
if unexportedField.CanAddr() {
// Get the memory address of the unexported field, as an unsafe.Pointer
fieldAddr := unexportedField.Addr().UnsafePointer() // *string
// Create a new *string Value that points to the exact same memory, but that we *can* set.
// reflect.NewAt returns a Value representing a pointer to a value of a specific type at a given address.
settablePrivateField := reflect.NewAt(unexportedField.Type(), fieldAddr).Elem()
// Now this Value thinks it's just a normal string pointer, and it's settable!
newStringValue := reflect.ValueOf("hacked")
settablePrivateField.Set(newStringValue)
}
fmt.Printf("Hacked struct: %+v\n", myInstance) // Prints: {PrivateField:hacked PublicField:42}
}
Use this power wisely, or more likely, don’t use it at all in production code. It’s a fantastic way to create fragile, unmaintainable code that breaks the second the internal structure of the package you’re poking changes. But it’s important to know it’s possible.
Setting Slices, Maps, and Other Reference Types
The rules are the same, but the mental model is easier. You’re not changing the header of the slice or the map pointer itself (usually), you’re changing the contents they point to. This is just like normal Go.
func main() {
// Modifying a slice element
slice := []string{"a", "b", "c"}
s := reflect.ValueOf(slice)
elem := s.Index(1) // Get the second element ("b")
if elem.CanSet() {
elem.Set(reflect.ValueOf("z"))
}
fmt.Println(slice) // [a z c]
// Modifying a map value is different. You can't get a settable Value for a map value directly.
// You must update the map the normal way: by assignment.
aMap := map[string]int{"one": 1}
mapValue := reflect.ValueOf(aMap)
key := reflect.ValueOf("one")
value := mapValue.MapIndex(key)
fmt.Println(value.Int()) // Can read it: 1
// value.Set(...) would panic. Cannot set map elements this way.
// Instead, to change a map value, you use `SetMapIndex`.
newValue := reflect.ValueOf(100)
mapValue.SetMapIndex(key, newValue) // This is equivalent to `aMap["one"] = 100`
fmt.Println(aMap) // map[one:100]
}
The SetMapIndex method is a bit of a oddball. It handles both setting new keys and updating existing ones, and if you set a value with a nil value, it deletes the key from the map. It’s a full key/value assignment operation wrapped into one method.