Para modificar un valor en tiempo de ejecución mediante el paquete reflect, el reflect.Value resultante debe ser direccionable. En Go, la mayoría de las veces pasamos variables por valor, lo que significa que el receptor de una función recibe una copia. Si intentas usar reflect.ValueOf(x) para modificar x, estarás operando sobre una copia local dentro de la función de reflexión, y los cambios se perderán o, más probablemente, el runtime lanzará un pánico. Para lograr la mutabilidad, necesitas pasar un puntero y luego “seguir” ese puntero mediante el método .Elem() para obtener un reflect.Value que apunte directamente a la dirección de memoria original.
Esto es fundamental porque el motor de reflexión de Go distingue entre un valor que simplemente “contiene” un dato y un valor que tiene una “ubicación” en la memoria que puede ser escrita. La lógica de diseño responde a la seguridad de tipos y a la naturaleza de la gestión de memoria de Go: si el runtime permitiera modificar cualquier reflect.Value sin verificar si es direccionable, se romperían las garantías de inmutabilidad de los valores pasados por copia y se introducirían condiciones de carrera impredecibles.
Realmente usarás esto cuando construyas infraestructura genérica: un ORM que mapea filas de una base de datos a campos de un struct, un deserializador de JSON personalizado o un motor de inyección de dependencias. En estos casos, no conoces la estructura del objeto hasta que el programa está corriendo.
Si intentas usar .Set() o métodos similares (SetInt, SetString) sobre un valor que no es direccionable o que es de un tipo incompatible, el programa sufrirá un panic inmediato. No es un error que puedas capturar con un recover de forma limpia para lógica de negocio; es un error de programación que indica que has perdido el rastro de la dirección de memoria original.
package main
import (
"errors"
"fmt"
"reflect"
)
// PopulateStruct es un simulador de ORM que mapea un mapa de datos a un struct.
func PopulateStruct(data map[string]any, target any) error {
// Obtenemos el reflect.Value del target.
// target debe ser un puntero para que podamos modificar el valor original.
v := reflect.ValueOf(target)
// Si pasamos un valor directamente en lugar de un puntero, v.Kind() será reflect.Struct.
// Si pasamos un puntero, v.Kind() será reflect.Ptr.
if v.Kind() != reflect.Ptr {
return errors.New("target debe ser un puntero a un struct")
}
// .Elem() desreferencia el puntero. Si v era un *User, ahora v es el User real
// y es direccionable, permitiéndonos llamar a .Set().
v = v.Elem()
if v.Kind() != reflect.Struct {
return errors.New("target debe ser un puntero a un struct")
}
// Iteramos sobre los datos que recibimos (simulando columnas de una DB).
for key, val := range data {
// Buscamos un campo en el struct que coincida con el nombre de la clave.
field := v.FieldByName(key)
// CanSet() es crítico: verifica si el campo es exportable (comienza con mayúscula)
// y si es direccionable.
if !field.IsValid() {
continue // El campo no existe en el struct
}
if !field.CanSet() {
return fmt.Errorf("el campo %s no es direccionable o es privado", key)
}
// Convertimos el valor recibido a reflect.Value para asegurar compatibilidad.
newValue := reflect.ValueOf(val)
// Verificamos que los tipos coincidan antes de intentar la asignación.
if field.Type() != newValue.Type() {
return fmt.Errorf("tipo incompatible para el campo %s: %s vs %s",
key, field.Type(), newValue.Type())
}
// Realizamos la asignación efectiva en la memoria del struct original.
field.Set(newValue)
}
return nil
}
type User struct {
ID int
Username string
Active bool
}
func main() {
// Datos que vendrían de una consulta SQL.
row := map[string]any{
"ID": 101,
"Username": "gopher_pro",
"Active": true,
}
// Creamos una instancia de User.
user := User{}
// Pasamos el puntero &user para que PopulateStruct pueda modificarlo.
err := PopulateStruct(row, &user)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Usuario cargado: %+v\n", user)
}
Análisis del código
En la función PopulateStruct, el primer paso crítico es v := reflect.ValueOf(target). Si llamamos a la función con user (el valor), v representa una copia del struct y v.CanSet() devolverá false. Por eso, la primera validación es asegurar que v.Kind() == reflect.Ptr.
Una vez confirmado que es un puntero, ejecutamos v = v.Elem(). Esta es la clave de la direccionabilidad. En el main, cuando pasamos &user, reflect.ValueOf(&user) nos da un objeto que representa la dirección de memoria. Al llamar a .Elem(), el reflect.Value resultante ya no representa la dirección, sino el espacio de memoria donde reside el User. Es este objeto el que tiene permiso para mutar la memoria original.
Dentro del bucle, utilizamos v.FieldByName(key) para localizar el campo. Es vital usar field.CanSet() antes de field.Set(newValue). Si intentáramos acceder a un campo privado (por ejemplo, si User tuviera id en minúsculas), FieldByName lo encontraría, pero CanSet() devolvería false porque el paquete reflect no puede violar las reglas de visibilidad de Go. Finalmente, field.Set(newValue) realiza la escritura directa en la dirección de memoria del campo, completando la operación de asignación que el compilador no puede realizar por sí solo al no conocer los nombres de los campos en tiempo de compilación.
El error frecuente
El error más común al trabajar con reflexión es olvidar desreferenciar el puntero o pasar el valor directamente.
// ERROR: Esto causará que CanSet() sea false o que no se modifique nada.
user := User{ID: 1}
data := map[string]any{"ID": 2}
// Caso A: Pasas el valor, no el puntero.
// v.Kind() será reflect.Struct, no reflect.Ptr.
// v.Elem() fallará o no será direccionable.
err := PopulateStruct(data, user)
// Caso B: Intentar usar .Set() sobre un valor obtenido de reflect.ValueOf(x)
// sin haber pasado un puntero previamente.
v := reflect.ValueOf(user)
v.Set(reflect.ValueOf(User{ID: 2})) // PANIC: reflect.Value.Set can only be called on a reflect.Value representing the actual object.
Para que reflect pueda escribir en un objeto, el flujo debe ser siempre:
Puntero Original $\rightarrow$ reflect.ValueOf(puntero) $\rightarrow$ .Elem() $\rightarrow$ field.Set().
N° 177