O que é Receiver em Go?
O receiver em Go é o parâmetro especial que vincula uma função a um tipo, transformando-a em um método. O receiver aparece entre a palavra-chave func e o nome do método, e é ele que define a qual tipo aquele método pertence.
Em Go, não existem classes. O conceito de receiver é o mecanismo que permite dar comportamento a tipos — sejam structs, tipos derivados de primitivos ou qualquer tipo definido pelo usuário. Junto com interfaces, o receiver é fundamental para o sistema de polimorfismo da linguagem.
// 'c' é o receiver, do tipo Circulo
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
Existem dois tipos de receiver em Go: value receiver (recebe uma cópia) e pointer receiver (recebe um ponteiro para o original). Essa distinção é central para entender como métodos funcionam na linguagem.
Value receiver vs pointer receiver
Value receiver
Com value receiver, o método recebe uma cópia do valor. Qualquer modificação dentro do método não afeta o valor original:
type Ponto struct {
X, Y float64
}
// Value receiver — opera sobre cópia
func (p Ponto) Distancia(outro Ponto) float64 {
dx := p.X - outro.X
dy := p.Y - outro.Y
return math.Sqrt(dx*dx + dy*dy)
}
// Esta tentativa de mover NÃO funciona como esperado
func (p Ponto) MoverErrado(dx, dy float64) {
p.X += dx // modifica apenas a cópia
p.Y += dy // o original permanece igual
}
func main() {
p := Ponto{X: 3, Y: 4}
p.MoverErrado(10, 10)
fmt.Println(p) // {3 4} — não mudou!
}
Pointer receiver
Com pointer receiver, o método recebe um ponteiro para o valor original e pode modificá-lo:
// Pointer receiver — opera sobre o original
func (p *Ponto) Mover(dx, dy float64) {
p.X += dx
p.Y += dy
}
func (p *Ponto) Escalar(fator float64) {
p.X *= fator
p.Y *= fator
}
func main() {
p := Ponto{X: 3, Y: 4}
p.Mover(10, 10)
fmt.Println(p) // {13 14} — modificou o original!
}
Note que Go faz a conversão automática: você pode chamar um método com pointer receiver em um valor (e vice-versa), desde que o valor seja endereçável:
p := Ponto{X: 1, Y: 2}
p.Mover(5, 5) // Go converte automaticamente para (&p).Mover(5, 5)
pp := &Ponto{X: 1, Y: 2}
pp.Distancia(Ponto{}) // Go converte automaticamente para (*pp).Distancia(...)
Quando usar cada tipo de receiver
| Critério | Value receiver | Pointer receiver |
|---|---|---|
| Modificar o receiver | Não | Sim |
| Struct grande (>64 bytes) | Evitar (cópia custosa) | Preferível |
| Tipos pequenos e imutáveis | Ideal | Desnecessário |
| Consistência com outros métodos | Se todos usam valor | Se algum usa ponteiro |
| Segurança em goroutines | Cópia isolada é segura | Requer mutex |
| Implementar interfaces | Ambos disponíveis em *T | Só disponível em *T |
Regra prática para decidir
// Use pointer receiver quando:
// 1. O método precisa modificar o receiver
func (u *Usuario) AtualizarEmail(novo string) {
u.Email = novo
}
// 2. A struct é grande (evita cópia em cada chamada)
type DadosGrandes struct {
Buffer [1024 * 1024]byte // 1MB
}
func (d *DadosGrandes) Processar() { /* ... */ }
// 3. Consistência — se um método usa ponteiro, todos devem usar
func (u *Usuario) Salvar() error { /* ... */ return nil }
func (u *Usuario) Validar() error { /* ... */ return nil }
func (u *Usuario) String() string { return u.Nome }
// ↑ Mesmo String() deveria usar *Usuario para consistência
// Use value receiver quando:
// 4. O tipo é pequeno e naturalmente imutável
type Cor struct{ R, G, B uint8 }
func (c Cor) Hex() string {
return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
}
Na dúvida, prefira pointer receiver — é a escolha mais segura e idiomática na comunidade Go.
Convenções de nomenclatura do receiver
Em Go, o nome do receiver deve ser curto — geralmente uma ou duas letras derivadas do nome do tipo:
type Cliente struct{}
func (c *Cliente) Salvar() error { return nil } // 'c' de Cliente
type HttpHandler struct{}
func (h *HttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {} // 'h' de HttpHandler
type QueryBuilder struct{}
func (qb *QueryBuilder) Build() string { return "" } // 'qb' de QueryBuilder
type DB struct{}
func (db *DB) Query(sql string) {} // 'db' de DB
Nunca use this ou self como nome do receiver — isso não é idiomático em Go e vai contra as convenções da comunidade. O receiver deve ter o mesmo nome em todos os métodos do tipo.
// ERRADO — não idiomático
func (this *Usuario) Nome() string { return this.nome }
func (self *Usuario) Email() string { return self.email }
// CORRETO — idiomático
func (u *Usuario) Nome() string { return u.nome }
func (u *Usuario) Email() string { return u.email }
Nil receiver
Em Go, é possível chamar métodos em receivers que são nil, desde que o método tenha pointer receiver e não tente acessar campos sem verificar:
type Lista struct {
Valor int
Proximo *Lista
}
func (l *Lista) Tamanho() int {
if l == nil {
return 0
}
return 1 + l.Proximo.Tamanho()
}
func (l *Lista) Soma() int {
if l == nil {
return 0
}
return l.Valor + l.Proximo.Soma()
}
func main() {
lista := &Lista{1, &Lista{2, &Lista{3, nil}}}
fmt.Println(lista.Tamanho()) // 3
fmt.Println(lista.Soma()) // 6
var vazia *Lista
fmt.Println(vazia.Tamanho()) // 0 — funciona!
}
Esse padrão é comum em estruturas de dados recursivas e em implementações de error customizados. A biblioteca padrão de Go usa nil receivers em vários lugares.
Method sets e interfaces
O tipo de receiver afeta diretamente quais interfaces um tipo pode satisfazer:
type Escritor interface {
Escrever(dados []byte) (int, error)
}
type Arquivo struct {
Nome string
conteudo []byte
}
// Pointer receiver — só *Arquivo satisfaz Escritor
func (a *Arquivo) Escrever(dados []byte) (int, error) {
a.conteudo = append(a.conteudo, dados...)
return len(dados), nil
}
func main() {
arq := Arquivo{Nome: "teste.txt"}
var w Escritor = &arq // OK: *Arquivo satisfaz Escritor
// var w2 Escritor = arq // ERRO: Arquivo não satisfaz Escritor
w.Escrever([]byte("hello"))
}
Essa regra existe porque quando um valor é armazenado em uma interface, Go pode não conseguir obter seu endereço de memória. Por segurança, apenas *T pode satisfazer interfaces que exigem pointer receivers. Para mais detalhes, veja a entrada sobre type.
Receiver e concorrência
Receivers de valor são naturalmente seguros para uso concorrente, pois cada goroutine recebe sua própria cópia. Receivers de ponteiro compartilham o mesmo dado e precisam de sincronização:
type Cache struct {
mu sync.RWMutex
dados map[string]string
}
func (c *Cache) Set(chave, valor string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.dados == nil {
c.dados = make(map[string]string)
}
c.dados[chave] = valor
}
func (c *Cache) Get(chave string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.dados[chave]
return val, ok
}
Para padrões avançados de concorrência, consulte o tutorial de padrões de concorrência e a entrada sobre channel.
Receiver em tipos derivados
Você pode adicionar receivers a qualquer tipo definido no seu package, não apenas structs:
type Temperatura float64
func (t Temperatura) EmCelsius() string {
return fmt.Sprintf("%.1f°C", float64(t))
}
func (t Temperatura) EmFahrenheit() string {
return fmt.Sprintf("%.1f°F", float64(t)*9/5+32)
}
type Palavras []string
func (p Palavras) Juntar(sep string) string {
return strings.Join(p, sep)
}
func (p Palavras) Contar() map[string]int {
contagem := make(map[string]int)
for _, palavra := range p {
contagem[palavra]++
}
return contagem
}
Isso é útil para criar tipos semânticos com comportamento próprio. Veja também a entrada sobre func para mais sobre funções e métodos.
Boas práticas
- Prefira pointer receiver na dúvida — evita bugs com mutação não aplicada e cópias desnecessárias
- Mantenha consistência — se um método usa pointer receiver, todos os métodos do tipo devem usar
- Nomes curtos —
c,s,db, nuncathisouself - Mesmo nome em todos os métodos — não use
cem um ecliem outro - Verifique nil — se o método pode ser chamado em nil receiver, trate o caso
- Documente comportamento — especialmente para métodos com pointer receiver que modificam o estado
Para aprofundar em métodos e receivers, veja o tutorial de Go para iniciantes e o guia sobre testes em Go.
Perguntas frequentes (FAQ)
Qual a diferença entre value receiver e pointer receiver?
Um value receiver recebe uma cópia do valor — modificações não afetam o original. Um pointer receiver recebe um ponteiro para o valor original e pode modificá-lo. A regra prática é usar pointer receiver quando o método precisa mutar o estado, a struct é grande, ou quando outros métodos do tipo já usam pointer receiver.
Por que não devo usar “this” ou “self” como nome do receiver?
Em Go, a convenção é usar nomes curtos derivados do tipo (como c para Cliente). Usar this ou self vem de outras linguagens e não é idiomático. A comunidade Go e ferramentas como golint alertam contra esse uso. O receiver em Go é apenas mais um parâmetro, não uma referência implícita como em Java ou Python.
Um método com pointer receiver pode ser chamado em um valor nil?
Sim, métodos com pointer receiver podem ser chamados em receivers nil. Isso é útil para estruturas recursivas e tipos que precisam de estado zero funcional. O método precisa verificar if receiver == nil antes de acessar campos para evitar panic. Esse padrão é comum na biblioteca padrão de Go.
Como o receiver afeta a implementação de interfaces?
O method set de T inclui apenas métodos com value receiver, enquanto o method set de *T inclui métodos com value e pointer receiver. Isso significa que se uma interface exige um método com pointer receiver, apenas *T satisfaz a interface — não T. Essa regra garante segurança de memória no sistema de tipos de Go.