Pointers vs. Values em Go: A linha entre performance e segurança
\n\nImagine a situação: você está programando em Go, tudo corre bem, mas de repente percebe que a aplicação começa a "travar" com grandes volumes de dados. Você abre o profiler — e vê que os allocs/op (alocações por operação) estão nas alturas. Familiar? Provavelmente, o problema está em como você passa dados para as funções e os retorna. A pergunta «Pointers vs. values in parameters and return values» é uma das mais quentes no Stack Overflow (milhares de visualizações!), e hoje vamos analisá-la de forma viva e com exemplos reais.
\n\nGo é uma linguagem que adora simplicidade. Mas essa simplicidade é enganosa: a escolha entre passar um valor (value) e um ponteiro (pointer) pode mudar drasticamente a velocidade do programa e o consumo de memória. Vamos entender quando usar cada um para que seu código voe, e não rasteje.
\n\nPor que este tópico é tão importante?
\n\nAo contrário de Java ou Python, onde tudo é uma referência a objeto, em Go você escolhe explicitamente: passar uma cópia da estrutura ou seu endereço. Parece um detalhe. Mas, na verdade, é uma decisão fundamental que afeta:
\n\n- \n
- Velocidade de execução — copiar estruturas grandes leva tempo. \n
- Consumo de memória — cópias extras = alocações extras. \n
- Segurança dos dados — ponteiros permitem mutar o original, valores não. \n
- Trabalho com interfaces — interfaces em Go são um mecanismo engenhoso que funciona de forma diferente com ponteiros e valores. \n
Vamos direto ao ponto e ver o código.
\n\nExemplo nº1: Estrutura pequena
\n\nVamos começar com algo simples. Suponha que temos uma estrutura Point com duas coordenadas:
type Point struct {\n X, Y float64\n}\n\n// Passagem por valor\nfunc MoveByValue(p Point, dx, dy float64) Point {\n p.X += dx\n p.Y += dy\n return p\n}\n\n// Passagem por ponteiro\nfunc MoveByPointer(p *Point, dx, dy float64) {\n p.X += dx\n p.Y += dy\n}\n\nNo primeiro caso, criamos uma cópia da estrutura, a modificamos e retornamos uma nova. No segundo, alteramos o original diretamente. Qual opção é mais rápida? Para uma estrutura tão pequena (16 bytes) — quase não há diferença. Copiar 16 bytes é uma operação atômica, e o GC (coletor de lixo) não cria carga extra. Além disso, passar por valor pode ser até mais rápido, porque o ponteiro requer um dereference adicional.
\n\nConclusão: Para estruturas pequenas (até 4-8 campos de tipos básicos), use valores sem medo. É mais seguro e não gera overhead.
\n\nExemplo nº2: Estrutura grande
\n\nAgora imagine que estamos trabalhando com dados de usuário:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n Avatar []byte // 1 MB de imagem\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n\nAqui, Avatar é um slice, que em Go é um tipo referencial (cabeçalho: ponteiro para os dados, comprimento, capacidade). Mas a própria estrutura, ao ser copiada, copiará apenas o cabeçalho do slice (24 bytes), e não os bytes em si. No entanto, se a estrutura tiver muitos campos, copiar 100+ bytes a cada chamada de função já é perceptível.
Vamos executar um benchmark simples:
\n\nfunc BenchmarkPassByValue(b *testing.B) {\n u := User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n for i := 0; i < b.N; i++ {\n _ = processUserByValue(u)\n }\n}\n\nfunc BenchmarkPassByPointer(b *testing.B) {\n u := &User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n for i := 0; i < b.N; i++ {\n _ = processUserByPointer(u)\n }\n}\n\nResultado (em hardware real):
\n- \n
- PassByValue: ~120 ns/op, 0 alocações \n
- PassBy