Alright, let’s talk about the three rules that make reflection in Go actually work. Think of these not as suggestions, but as the fundamental physics of the reflect package. If you ignore them, your code will blow up, and it will be a very confusing explosion. I’ve been there. It’s not pretty.

These rules govern how Go values move between the ordinary world of int, string, and struct and the mirrored world of reflect.Value and reflect.Type. Master them, and you hold the power to metaprogram. Mess them up, and you’ll spend an hour wondering why a perfectly good value is suddenly “invalid.”

The First Law: Reflection goes from interface value to reflection object

This is the entry fee. You can’t get a reflect.Value out of thin air; you can only get one by first stuffing your value into an empty interface{} (or any interface, really) and then unpacking it with reflect.ValueOf().

Why? Because reflection is inherently about interface types. An interface variable stores a two-tuple: the underlying concrete value and its static type. The reflect.ValueOf function simply reaches into that interface variable and extracts that information, wrapping it up in a nice struct for you to interrogate.

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    
    // This is the magic incantation. Notice how we pass x to an interface{} parameter.
    v := reflect.ValueOf(x)
    t := reflect.TypeOf(x)
    
    fmt.Println("Value:", v)           // Value: 3.4
    fmt.Println("Type:", t)            // Type: float64
    fmt.Println("Kind:", v.Kind())     // Kind: float64
    fmt.Println("Float value:", v.Float()) // Float value: 3.4
}

The key thing to note here is that v is a reflect.Value representation of the float64, not the float64 itself. To get the original value back, you have to use a method like v.Float().

The Second Law: Reflection goes from reflection object to interface value

This is the reverse journey. Once you’ve finished poking and prodding at a reflect.Value, you’ll probably want to turn it back into a regular Go value you can use. You do this by calling the Interface() method on the reflect.Value. This method packs the value and its type back up into an empty interface, which you can then type-assert back to its concrete type.

Why is this necessary? Because the reflect package is deliberately low-level. It doesn’t know what you want to do with the value; it just gives you a generic container. It’s your job to know what type should be in that container and to unpack it safely.

// ... continuing from the previous code

// Convert the reflection object back to an interface{}
i := v.Interface()

// Type assert the interface{} back to a float64 (this will panic if we're wrong)
y := i.(float64)
fmt.Println("Value after round-trip:", y) // Value after round-trip: 3.4

// A safer way, using a switch for type.
switch val := i.(type) {
case float64:
    fmt.Println("It's definitely a float64:", val)
default:
    fmt.Println("No idea what this is")
}

The Third Law: To modify a reflection object, the value must be settable

This is the big one. This is the rule that causes 90% of the head-scratching. You can’t just change any old reflect.Value you get your hands on. The Value must be settable.

A Value is settable only if it represents a value that can be changed in the original, non-reflected world. In practical terms, this means it must be addressable. If you have a pointer, you can change what it points to. If you have the variable itself, you can change it. If you passed a literal (like 3.4) to reflect.ValueOf, you can’t change it because 3.4 = 5.1 is not valid Go.

The reflect.ValueOf function always receives a copy of the value you pass in (since it’s passed via an interface{}). So if you pass the variable x directly, reflect.ValueOf(x) receives a copy of x. Modifying that copy would be useless, so Go rightly makes it non-settable.

func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    fmt.Println("Settability of v:", v.CanSet()) // Settability of v: false

    // This will panic: "panic: reflect: reflect.Value.SetFloat using unaddressable value"
    // v.SetFloat(7.1)
    
    // To make it settable, we must pass a pointer to x.
    p := reflect.ValueOf(&x) // Get a reflect.Value for the pointer (*float64)
    fmt.Println("Settability of p:", p.CanSet()) // Still false! p is a pointer, not the float.
    
    // We need to "dereference" the pointer using Elem().
    // This gets the Value that the pointer points to.
    v = p.Elem()
    fmt.Println("Settability of v (now via Elem()):", v.CanSet()) // Now true!
    
    // Now we can finally change the value.
    v.SetFloat(7.1)
    fmt.Println("x is now:", x) // x is now: 7.1
}

The Elem() method is how you follow a pointer to get the underlying, settable value. It’s the reflection equivalent of the * operator. If you forget this step and try to Set the pointer itself, you’re trying to assign a new memory address to the pointer variable, which is rarely what you want and is, rightly, forbidden unless you have the address of the pointer variable itself. It’s pointers all the way down, and Elem() is your ladder back up.