Value receiver vs. pointer receiver: um duelo que não acaba
\n\nSe 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\nSpoiler: 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\nNeste 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\nO que é receiver em Go?
\n\nPara 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\ntype 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\nNo 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\nQuando usar value receiver
\n\nVamos começar pela opção mais simples e segura.
\n\n1. O método não modifica o receptor
\n\nEssa é 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\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nNinguém espera que calcular a área mude o próprio retângulo. Value receiver aqui é a escolha ideal.
\n\n2. Structs pequenas
\n\nSe 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\n3. 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\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nEsse é 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\nQuando escolher pointer receiver
\n\nAqui começa a parte mais interessante. Pointer receiver é uma ferramenta poderosa, mas é preciso ter cuidado com ela.
\n\n1. O método modifica o receptor
\n\nEsse é o caso mais óbvio. Se você está escrevendo SetName, UpdateStatus ou Increment — você precisa de um ponteiro.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nSem o ponteiro, você estará incrementando uma cópia, e o original permanecerá inalterado. Um erro clássico de iniciantes.
\n\n2. Structs grandes
\n\nSe 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\ntype 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\nAqui, pointer receiver não é apenas conveniência, mas sim uma otimização de desempenho.
\n\n