Value vs Pointer Receiver en Go: ¿qué elegir?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Value receiver vs. pointer receiver: un duelo que no cesa

\n\n

Si 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\n

Spoiler: 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\n

En 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\n

Para 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\n
type 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\n

En 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\n

Cuándo usar value receiver

\n\n

Empecemos por la opción más simple y segura.

\n\n

1. El método no modifica al receptor

\n\n

Esta 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\n
type Rectangle struct {\n    Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n    return r.Width * r.Height\n}\n
\n\n

Nadie espera que calcular el área modifique el rectángulo en sí. Value receiver aquí es la elección ideal.

\n\n

2. Estructuras pequeñas

\n\n

Si 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\n

3. Seguridad e inmutabilidad

\n\n

A 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\n
type Config struct {\n    Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n    c.Timeout = t\n    return c\n}\n
\n\n

Este es el patrón de programación funcional: el método devuelve una nueva copia sin tocar el original. Ideal para configuraciones inmutables.

\n\n

Cuándo elegir pointer receiver

\n\n

Aquí empieza lo interesante. Pointer receiver es una herramienta poderosa, pero hay que usarla con cuidado.

\n\n

1. El método modifica al receptor

\n\n

Este es el caso más obvio. Si escribes SetName, UpdateStatus o Increment, necesitas un puntero.

\n\n
type Counter struct {\n    Value int\n}\n\nfunc (c *Counter) Increment() {\n    c.Value++\n}\n
\n\n

Sin puntero, estarías incrementando una copia y el original quedaría sin cambios. Un error clásico de principiantes.

\n\n

2. Estructuras grandes

\n\n

Si 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\n
type 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\n

Aquí, pointer receiver no es solo comodidad, sino una optimización de rendimiento.

\n\n

3. Cuando la estructura contiene un mutex o

Blogs

Book Recommendations