Value vs Pointer Receiver em Go: o que escolher?

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Value receiver vs. pointer receiver: um duelo que não acaba

\n\n

Se você já escreveu em Go pelo menos uma vez, certamente já se deparou com essa questão. Você abre o Stack Overflow — e lá estão milhares de visualizações, centenas de comentários, e cada segundo iniciante (e não só) quebra a cabeça: "O que devo escolher? Value receiver ou pointer receiver?"

\n\n

Spoiler: não existe uma resposta correta para todas as situações. Mas existem regras claras que ajudam você a não errar em 95% dos casos. E hoje vamos analisá-las de uma vez por todas.

\n\n

Neste artigo, não vou apenas traduzir a documentação técnica. Vamos passar por casos reais, observar o desempenho, a mutabilidade, as interfaces e até como a comunidade Go enxerga as diferentes abordagens. Vamos nessa!

\n\n

O que é receiver em Go?

\n\n

Para quem está começando com a linguagem: receiver é um parâmetro que vincula uma função a um tipo. É assim que os métodos são implementados em Go.

\n\n
type User struct {\n    Name string\n    Age  int\n}\n\n// Value receiver\nfunc (u User) Greet() string {\n    return "Olá, eu sou " + u.Name\n}\n\n// Pointer receiver\nfunc (u *User) Birthday() {\n    u.Age++\n}\n
\n\n

No primeiro caso, trabalhamos com uma cópia da struct. No segundo, com um ponteiro para o original. Parece um detalhe, mas desse detalhe surgem decisões arquiteturais inteiras.

\n\n

Quando usar value receiver

\n\n

Vamos começar pela opção mais simples e segura.

\n\n

1. O método não modifica o receptor

\n\n

Essa é a regra de ouro. Se o método apenas lê dados e não altera nada — use value receiver. É seguro, previsível e 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

Ninguém espera que calcular a área mude o próprio retângulo. Value receiver aqui é a escolha ideal.

\n\n

2. Structs pequenas

\n\n

Se sua struct tem apenas alguns campos (como Rectangle acima), copiá-la praticamente não custa nada. Na verdade, pode ser mais rápido do que trabalhar com um ponteiro, porque os dados ficam na pilha, e não no heap.

\n\n

3. Segurança e imutabilidade

\n\n

Às vezes você quer intencionalmente garantir que o método não altere os dados originais. O value receiver oferece essa garantia em nível de 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

Esse é o padrão de programação funcional: o método retorna uma nova cópia, sem tocar no original. Ideal para configurações imutáveis.

\n\n

Quando escolher pointer receiver

\n\n

Aqui começa a parte mais interessante. Pointer receiver é uma ferramenta poderosa, mas é preciso ter cuidado com ela.

\n\n

1. O método modifica o receptor

\n\n

Esse é o caso mais óbvio. Se você está escrevendo SetName, UpdateStatus ou Increment — você precisa de um ponteiro.

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

Sem o ponteiro, você estará incrementando uma cópia, e o original permanecerá inalterado. Um erro clássico de iniciantes.

\n\n

2. Structs grandes

\n\n

Se sua struct pesa perto de cem bytes ou mais, copiá-la a cada chamada de método é caro. Especialmente se o método for chamado em um loop.

\n\n
type LargeData struct {\n    Buffer [1024]byte\n    Meta   map[string]interface{}\n}\n\n// Copiar isso a cada chamada é um sofrimento\nfunc (l *LargeData) Process() {\n    // trabalhamos com o original\n}\n
\n\n

Aqui, pointer receiver não é apenas conveniência, mas sim uma otimização de desempenho.

\n\n

3. Quando a struct contém um mutex ou

Blogs

Book Recommendations