Value receiver vs. pointer receiver: un duelo que no cesa
\n\nSi alguna vez has escrito en Go, seguro te has topado con esta pregunta. Abres Stack Overflow y ves miles de visitas, cientos de comentarios, y cada segundo novato (y no tan novato) se rompe la cabeza: "¿Qué elijo? ¿Value receiver o pointer receiver?"
\n\nSpoiler: no hay una respuesta correcta para todos los casos. Pero sí hay reglas claras que te ayudarán a no equivocarte en el 95% de los casos. Y hoy las analizaremos de una vez por todas.
\n\nEn este artículo no solo traduciré documentación técnica. Revisaremos casos reales, veremos rendimiento, mutabilidad, interfaces e incluso cómo la comunidad Go ve los diferentes enfoques. ¡Allá vamos!
\n\n¿Qué es un receiver en Go?
\n\nPara quienes recién conocen el lenguaje: un receiver es un parámetro que vincula una función con un tipo. Así es como se implementan los métodos en Go.
\n\ntype User struct {\n Name string\n Age int\n}\n\n// Value receiver\nfunc (u User) Greet() string {\n return "Hola, soy " + u.Name\n}\n\n// Pointer receiver\nfunc (u *User) Birthday() {\n u.Age++\n}\n\n\nEn el primer caso trabajamos con una copia de la estructura. En el segundo, con un puntero al original. Parece un detalle menor, pero de este detalle surgen decisiones arquitectónicas completas.
\n\nCuándo usar value receiver
\n\nEmpecemos por la opción más simple y segura.
\n\n1. El método no modifica al receptor
\n\nEsta es la regla de oro. Si el método solo lee datos y no cambia nada, usa value receiver. Es seguro, predecible y claro.
\n\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nNadie espera que calcular el área modifique el rectángulo en sí. Value receiver aquí es la elección ideal.
\n\n2. Estructuras pequeñas
\n\nSi tu estructura tiene solo un par de campos (como Rectangle arriba), copiarla prácticamente no cuesta nada. De hecho, puede ser más rápido que trabajar con un puntero, porque los datos están en la pila, no en el montón.
\n\n3. Seguridad e inmutabilidad
\n\nA veces intencionalmente quieres garantizar que el método no modifique los datos originales. Value receiver te da esa garantía a nivel del compilador.
\n\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nEste es el patrón de programación funcional: el método devuelve una nueva copia sin tocar el original. Ideal para configuraciones inmutables.
\n\nCuándo elegir pointer receiver
\n\nAquí empieza lo interesante. Pointer receiver es una herramienta poderosa, pero hay que usarla con cuidado.
\n\n1. El método modifica al receptor
\n\nEste es el caso más obvio. Si escribes SetName, UpdateStatus o Increment, necesitas un puntero.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nSin puntero, estarías incrementando una copia y el original quedaría sin cambios. Un error clásico de principiantes.
\n\n2. Estructuras grandes
\n\nSi tu estructura pesa cien bytes o más, copiarla en cada llamada al método es costoso. Especialmente si el método se llama en un bucle.
\n\ntype LargeData struct {\n Buffer [1024]byte\n Meta map[string]interface{}\n}\n\n// Copiar esto en cada llamada es un dolor\nfunc (l *LargeData) Process() {\n // trabajamos con el original\n}\n\n\nAquí, pointer receiver no es solo comodidad, sino una optimización de rendimiento.
\n\n