Paréntesis extraños antes del nombre de la función en Go: análisis detallado
Imagine: abre un código ajeno en Go y ve algo que le hace saltar un ojo:
func (u User) Greet() string { return "Hola, " + u.Name}Alto. ¿Qué es ese (u User) antes del nombre de la función? En Python para los métodos se usa def greet(self):, en Java — public String greet() dentro de una clase. Y aquí — unos paréntesis, y además con un tipo dentro. ¿Una locura? ¡No, es genial!
Esta construcción es una de las características más potentes de Go que a menudo desconcierta a los principiantes. En realidad, es un receiver (receptor) — la clave para entender la programación orientada a objetos en Go.
En este artículo, miraremos bajo el capó del compilador de Go, analizaremos cómo funcionan estos paréntesis y por qué sin ellos su código parecerá de los años 90. ¿Listo? ¡Vamos allá!
¿Qué es un receiver y para qué sirve?
En Go no hay clases en el sentido clásico. Pero hay estructuras (struct) y métodos. Los paréntesis antes del nombre de la función son precisamente la declaración de un método para un tipo concreto. Técnicamente, esto se llama receiver parameter.
La sintaxis es simple:
func (receiver_name ReceiverType) MethodName(params) return_type { // cuerpo del método}Aquí receiver_name es una variable a través de la cual se accede a los campos de la estructura (como self en Python o this en Java). Y ReceiverType es su tipo (generalmente una estructura).
Ejemplo de la vida real:
package main
import "fmt"
type User struct { Name string Age int}
// Método con receiverfunc (u User) SayHello() { fmt.Printf("¡Hola! Soy %s, tengo %d años.", u.Name, u.Age)}
func main() { user := User{Name: "Alicia", Age: 25} user.SayHello() // Salida: ¡Hola! Soy Alicia, tengo 25 años.}Observe: llamamos al método mediante un punto, como en cualquier lenguaje POO. Pero bajo el capó, Go hace un truco ingenioso.
Value receiver vs Pointer receiver: batalla de titanes
En Go hay dos tipos de receivers: por valor (value receiver) y por puntero (pointer receiver). La diferencia es colosal, y no entender esto es una causa frecuente de errores.
Value receiver
Cuando escribe func (u User) ..., Go copia toda la estructura. El método trabaja con una copia del objeto. Los cambios dentro del método no afectarán al original.
func (u User) Birthday() { u.Age++ // ¡Esto solo cambiará la copia!}
func main() { user := User{Name: "Bob", Age: 30} user.Birthday() fmt.Println(user.Age) // ¡Sigue siendo 30! ¿Error?}Este código no dará error de compilación, pero es lógicamente incorrecto. Espera que la edad aumente, pero permanece igual. ¡Bienvenido al mundo del value receiver!
Pointer receiver
Si usa un puntero: func (u *User) ..., el método recibe una referencia al objeto original. Ahora los cambios serán visibles para todos.
func (u *User) Birthday() { u.Age++ // Modificamos el original}
func main() { user := &User{Name: "Bob", Age: 30} // Creamos un puntero user.Birthday() fmt.Println(user.Age) // ¡31! ¡Funciona!}Regla general: si el método modifica el estado de la estructura o la estructura es grande (para evitar copias), use pointer receiver. Si el método solo lee datos, use value receiver.
¿Cómo funciona bajo el capó?
Ahora lo más interesante. Cuando el compilador de Go ve:
type Rectangle struct { Width, Height float64}
func (r Rectangle) Area() float64 { return r.Width * r.Height}Lo convierte en una función normal, donde el receiver se convierte en el primer argumento:
Blogs
Book Recommendations
Leer también
Cómo escribir una API REST en Java en 30 minutos: guía práctica
Immutable vs const en Rust: ¿cuál es la diferencia? Guía 2025
Ejecutando comandos en Rust: cómo capturar la salida en 2025
CQRS y Event Sourcing en Kotlin: arquitectura para un código impecable
Monorepositorio con C++: organización del código en la práctica