En Go, las estructuras (struct) se transmiten y retornan por defecto por valor. Esto significa que al llamar a una función o al devolverla, se copia toda la estructura. Para estructuras pequeñas esto es transparente, pero para las grandes, es una cuestión crítica.
Inicialmente, Go estaba orientado a un rendimiento eficiente con un bajo número de asignaciones. Sin embargo, el peligro de la copia inadvertida de grandes datos surgió cuando las estructuras utilizan muchos campos y objetos anidados. El rendimiento de tales operaciones puede verse afectado, y a veces la diferencia solo se detecta en el perfilado o en el dolor del GC.
Si una estructura tiene un tamaño grande, su copia en cada llamada a la función, retorno o asignación resulta costosa. Esto lleva a:
Para grandes estructuras, se recomienda transmitir y devolver un puntero a la estructura (*T), en lugar del mismo objeto. Esto reduce los costos y asegura que se trabaje con una sola instancia de datos.
Ejemplo de código:
package main import "fmt" type Large struct { Data [1024]int } // Transmisión por valor (incorrecto para objetos grandes) func ValueProcess(l Large) { l.Data[0] = 123 // solo cambia la copia } // Transmisión por puntero func PointerProcess(l *Large) { l.Data[0] = 456 // cambia el original } func main() { a := Large{} ValueProcess(a) fmt.Println("Después de ValueProcess:", a.Data[0]) // 0 PointerProcess(&a) fmt.Println("Después de PointerProcess:", a.Data[0]) // 456 }
Características clave:
1. ¿Se puede devolver un puntero a una variable local de estructura desde una función en Go?
Sí. Go garantiza la validez de tales punteros, moviendo automáticamente a la pila los valores a los que apunta el puntero devuelto (escape to heap).
func NewLarge() *Large { l := Large{} return &l }
2. ¿Cambiará el original si se pasa una estructura a la función por valor y se cambian los campos dentro?
No: solo cambiará la copia, y el original fuera de la función permanecerá igual.
3. ¿Siempre hay que usar punteros para estructuras?
No. Para estructuras pequeñas (con unos pocos campos), la transmisión por valor es segura y a menudo preferible (inmutable/semantic value), ahorrando en asignaciones y reduciendo la carga en el GC.
En un servicio de registro, cada evento representaba una gran estructura y se devolvía desde funciones por valor: cada cambio copiaba la estructura entera.
Ventajas:
Desventajas:
Se llegó a transmitir y devolver estructuras por puntero, cambiando los datos a través de las firmas tipo func(l *Large) y func() *Large.
Ventajas:
Desventajas: