Métodos: Receptores de Valor vs. Puntero
Los métodos adjuntan comportamiento a los tipos a través de receptores: una copia (T) o un puntero (*T).
Busca en todas las páginas de la documentación
Los métodos adjuntan comportamiento a los tipos a través de receptores: una copia (T) o un puntero (*T).
La elección afecta la mutabilidad, las asignaciones, la satisfacción de interfaces y la consistencia de la API en tu paquete.
Un receptor de valor copia la estructura para la llamada al método.
Las mutaciones dentro del método no afectan la copia del llamador.
Un receptor de puntero comparte el valor subyacente.
Las mutaciones persisten y evitan copiar estructuras grandes.
Los conjuntos de métodos determinan la satisfacción de la interfaz: el tipo de valor T solo incluye métodos con receptores de valor; el tipo de puntero *T incluye métodos con receptores de valor y puntero.
Elige un estilo de receptor por tipo a menos que tengas una excepción documentada.
Usa receptores de puntero cuando los métodos mutan el estado o cuando el tamaño de la estructura hace que la copia sea costosa.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
type Buffer struct {
b []byte
}
// Receptor de puntero: muta y coincide con APIs estilo io.Writer.
func (buf *Buffer) Write(p []byte) (int, error) {
buf.b = append(buf.b, p...)
return len(p), nil
}
// Receptor de valor: instantánea de solo lectura.
func (buf Buffer) Bytes() []byte {
return append([]byte(nil), buf.b...)
}
// Consistencia: si algún método necesita *T, prefiere *T para todos los métodos.
func (buf *Buffer) Reset() { buf.b = buf.b[:0] }Cuándo usar esto:
Inc, Write, SetState).fmt.Stringer en estructuras grandes está bien de cualquier manera; sync.Mutex no debe copiarse).time.Time usa métodos de valor).T como de *T.package ledger
import "fmt"
type Account struct {
id string
balance int64
}
func NewAccount(id string) *Account {
return &Account{id: id}
}
func (a *Account) Credit(cents int64) error {
if cents < 0 {
return fmt.Errorf("ledger: negative credit %d", cents)
}
a.balance += cents
return nil
}
func (a Account) Balance() int64 {
return a.balance
}
type Creditor interface {
Credit(cents int64) error
}
func Process(c Creditor, amount int64) error {
return c.Credit(amount)
}
func Example() error {
acct := NewAccount("user-1")
if err := Process(acct, 500); err != nil {
return err
}
return nil
}Lo que esto demuestra:
Credit muta el saldo.Balance devuelve una instantánea sin exponer estado mutable.Creditor requiere Credit; satisfecha por *Account, no por el valor Account.*Account para que los llamadores lleguen al conjunto de métodos de puntero.a.Credit(x) se convierte en Credit(a, x) con el receptor como primer argumento.T: métodos con receptor T*T: métodos con receptor T y *T| Señal | Preferir |
|---|---|
| Los campos del receptor mutan | *T |
La estructura contiene sync.Mutex u otros campos que no se copian | Solo *T |
| Tipo de valor inmutable pequeño | T |
| Estructura grande (más de unos pocos punteros) | *T por rendimiento |
| Receptores mixtos en el mismo tipo | Evitar - elegir *T si existe alguna mutación |
| Receptor del método | ¿var x T satisface? | ¿var p *T satisface? |
|---|---|---|
func (T) M() | Sí | Sí |
func (*T) M() | No | Sí |
type S struct{ n int }
func (s S) V() int { return s.n }
func (s *S) P() int { s.n++; return s.n }
var i interface {
V() int
P() int
}
// i = S{} // error de compilación: S le falta P
i = &S{} // OKDocumenta los constructores que devuelven *T cuando existen métodos de puntero.
*T o devolver explícitamente una copia actualizada.sync.Mutex y provocan pánico. Solución: solo receptores de puntero; a veces, hacer la estructura no exportable.func (T) Foo y func (*T) Bar rompen las asignaciones de interfaz T. Solución: unificar en *T cuando exista cualquier método de puntero.var p *T; p.Method() si el método maneja nulos (ver guardas nil en la biblioteca estándar). Solución: documentar o provocar un pánico temprano para nulos inválidos.Account a Creditor falla. Solución: almacenar *Account o agregar envolturas de receptor de valor solo cuando sea semánticamente sólido.*bytes.Buffer.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Funciones de paquete func F(t *T) | No se necesitan conjuntos de métodos | Implementar interfaces estándar |
Patrón de retorno-nuevo inmutable func (t T) WithX() T | Tipos de valor pequeños como configuración | Estructuras grandes o rutas de acceso frecuentes |
| Incrustación de interfaces | Componer comportamiento | Ocultar reglas de receptor detrás de tipos opacos |
| Genéricos en funciones | Algoritmos sobre tipos | Necesidad de despacho de métodos virtuales |
El conjunto de métodos adjuntos a un tipo utilizado para la satisfacción de interfaces.
T y *T tienen conjuntos diferentes cuando existen métodos solo de puntero.
Muchos equipos usan *T por defecto para estructuras para dejar espacio para la mutación y evitar sorpresas de copia.
Los receptores de valor siguen siendo correctos para tipos inmutables muy pequeños.
Ergonomía: v.Method() funciona cuando v es direccionable y el método tiene un receptor de puntero.
Los valores no direccionables (elementos de mapa, resultados de funciones) pueden no calificar.
Sí, si todos los métodos usan T, tanto los valores T como *T satisfacen la interfaz.
Los métodos solo de puntero restringen la satisfacción a *T.
Los receptores no cambian las reglas de comparación de estructuras.
Las estructuras comparables todavía se comparan campo por campo; los slices/mapas internos hacen que la estructura no sea comparable.
Exponen la mutación compartida, lo cual es intencional.
La inmutabilidad se aplica por disciplina o devolviendo copias, no solo por receptores de valor.
Define tipos nombrados: type Celsius float64 luego func (c Celsius) F() Fahrenheit.
No se pueden adjuntar métodos a int incorporado directamente.
Los receptores de valor de estructuras grandes copian en cada llamada.
Realiza perfiles antes de micro-optimizar; los receptores de puntero generalmente ganan para estructuras grandes.
Patrón común: func (s *Server) ServeHTTP(...) compartiendo dependencias en Server.
Los manejadores pequeños y sin estado pueden ser funciones simples.
Los reconciliadores son estructuras con receptores de puntero que mutan el estado y llaman a client.Client.
Sigue las convenciones de scaffold generadas para el cumplimiento de la interfaz.
Posible pero confuso para el diseño de interfaces.
Prefiere un estilo de receptor exportado consistente.
Herramientas como staticcheck marcan mutexes copiados y nombres de receptores inconsistentes.
Ejecuta golangci-lint en CI.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado de Green Tea GC, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de la placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026