En Go, los métodos se pueden declarar tanto para el valor como para el puntero de un tipo (receptor de valor / receptor de puntero). Esta característica se ha mantenido desde las primeras versiones del lenguaje para un control explícito sobre quién modificará los datos originales. El clásico problema es la necesidad de distancia entre la semántica de valor (copia, no modifica) y puntero (acceso compartido a los datos y posibilidad de modificación).
Problema — es fácil cometer un error al declarar un método con receptor de valor y no obtener el efecto esperado, o al invocar un método de valor en una variable de puntero.
Solución — seguir las siguientes reglas:
Ejemplo de código:
type Counter struct { Value int } func (c Counter) IncCopy() { c.Value++ } // receptor de valor func (c *Counter) IncPointer() { c.Value++ } // receptor de puntero c := Counter{} c.IncCopy() // Value permanecerá 0 c.IncPointer() // Value se convertirá en 1
Características clave:
¿Se puede invocar un método de receptor de valor en un puntero, y un método de puntero en un valor?
Go "bajo el capó" desreferencia automáticamente los punteros o toma su dirección, por lo que la invocación está permitida si los tipos son compatibles. Pero no siempre: con las interfaces esto no funciona de manera predecible.
var c Counter (&c).IncCopy() // Se puede llamar al método de valor a través del puntero c.IncPointer() // Se puede llamar al método de puntero, Go tomará automáticamente la dirección
¿Qué pasará si una estructura implementa solo métodos de puntero, pero se pasa por valor a una interfaz?
Ese objeto no implementará la interfaz si requiere métodos de puntero, por lo que es posible un panic o un error de compilación.
type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // ¡error! Counter por valor no implementa la interfaz f(&c) // correcto
¿Cambiará la estructura al invocar un método de receptor de puntero si se pasa una copia del puntero?
Sí, incluso si se copia el puntero, el mismo objeto subyacente está presente, por lo que el resultado será el mismo.
c := Counter{} p := &c p2 := p p2.IncPointer() // Value aumentará
Un ingeniero implementa una estructura con métodos de receptor de valor "Update". A través de una interfaz se pasa la estructura, pero los cambios "desaparecen" — porque se trabaja con una copia.
Ventajas:
Desventajas:
Un acuerdo explícito en el equipo: todos los métodos que cambian el estado solo utilizan receptor de puntero, las interfaces se implementan solo con punteros, el valor se utiliza para "extensiones" y utilidades.
Ventajas:
Desventajas: