& e *
Um ponteiro guarda o endereço de memória de um valor. &x dá um ponteiro para x. *p segue o ponteiro até o valor.
O asterisco tem dois papéis. Em um tipo (*int) ele significa "ponteiro para int". Em uma expressão (*p) ele significa "o valor para o qual p aponta". Imprimir o próprio p mostra um endereço como 0xc000012345, que muda de uma execução para outra.
Por que ponteiros existem: compartilhar em vez de copiar
Go passa tudo por valor. Uma função recebe uma cópia de cada argumento, então não consegue mudar a variável de quem chama. Passar um ponteiro copia o endereço, e os dois lados chegam ao mesmo valor.
Go não tem referências no sentido do C++. "Passagem por referência" em Go significa passar um ponteiro por valor.
Ponteiros para structs
Ponteiros para structs são os ponteiros mais comuns em código Go. O acesso a campos desreferencia automaticamente: p.Name significa (*p).Name.
Métodos seguem a mesma lógica: um receiver ponteiro permite que o método altere a struct. A página de métodos cobre a escolha entre receiver de valor e de ponteiro.
new
new(T) aloca um T zerado e devolve o seu endereço. É o mesmo que declarar uma variável e pegar o endereço dela.
p := new(int) // *int pointing at 0
var x int
q := &x // same thing, two lines
a := new(Account) // same as &Account{}
Na prática, &T{...} é mais comum para structs, porque permite definir campos na mesma expressão. new é prático para ponteiros de tipos básicos. Não o confunda com make, que constrói slices, maps e channels e os devolve prontos para uso, não como ponteiros.
Devolver um ponteiro para uma variável local é seguro
Em C, devolver o endereço de uma variável local é um bug. Em Go é código comum.
A escape analysis do compilador decide se um valor vive na pilha ou no heap. Você nunca escolhe e nunca libera memória: o coletor de lixo a recupera quando nada mais aponta para ela. go build -gcflags=-m imprime essas decisões, se você tiver curiosidade.
Ponteiros nil
O valor zero de qualquer tipo ponteiro é nil. Desreferenciar nil causa panic.
Saída:
1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference
Sem o recover, o programa cairia com essa mensagem e um stack trace começando com panic: runtime error: invalid memory address or nil pointer dereference e [signal SIGSEGV: segmentation violation ...]. As origens de sempre:
- uma variável ponteiro ou campo de struct que nunca recebeu valor
m[key]em ummap[string]*Tpara uma chave ausente, que devolvenil- ignorar um erro:
f, _ := os.Open(path)deixafnil em caso de falha - um método com receiver ponteiro chamado em um ponteiro nil, que depois lê um campo
Chamar um método em um ponteiro nil é, por si só, permitido. O panic só acontece quando o método acessa os campos do receiver.
Quando usar um ponteiro
Use um ponteiro quando:
- uma função ou método precisa mudar o valor de quem chama
- o valor é uma struct grande e é passado de um lado para o outro com frequência
- o tipo não pode ser copiado (contém um
sync.Mutex, ou representa um recurso único, como uma conexão) - você precisa expressar "nenhum valor" com
nil, por exemplo um campo opcional em uma struct JSON (*intdiferencia "ausente" de0)
Não use um ponteiro quando:
- o valor é pequeno e só de leitura (
time.Time, umPoint) - o tipo já se comporta como referência: slices, maps, channels, funções e interfaces raramente precisam de
*na frente. Um*[]intou*map[string]intquase sempre é um erro
Ponteiros não são automaticamente mais rápidos. Copiar uma struct pequena é barato, e um ponteiro pode forçar uma alocação no heap e dar mais trabalho ao coletor de lixo.
Ponteiros e variáveis de laço
Pegar o endereço de uma variável de laço é seguro desde o Go 1.22, porque cada iteração tem a sua própria variável:
Isto imprime 0 1 2. Antes do Go 1.22 (que também trouxe range 3), o mesmo laço escrito como for i := 0; i < 3; i++ imprimia 3 3 3, já que os três ponteiros compartilhavam uma única variável. Repare que &v em for _, v := range items aponta para uma cópia do elemento, não para o elemento no slice. Para um ponteiro para o próprio elemento, use &items[i].
Erros comuns
- Desreferenciar sem verificar. Verifique
if p == nilonde nil for possível. - Ponteiro para o valor do range.
&vaponta para uma cópia; use&s[i]. - Ponteiros para slices e maps. Raramente necessários. Devolva o slice novo.
- Guardar
&s[i]através de umappend. Se oappendrealocar, o ponteiro ainda se refere ao array antigo e as escritas posteriores não aparecem em lugar nenhum.
Perguntas frequentes
O que & e * significam em Go?
&x pega o endereço de x e dá um ponteiro do tipo *T. *p desreferencia o ponteiro: lê ou escreve o valor para o qual ele aponta. Em um tipo, *T significa "ponteiro para T".
Qual a diferença entre new e make em Go?
new(T) aloca um T zerado e devolve um *T; funciona com qualquer tipo. make só funciona com slices, maps e channels, e devolve um valor inicializado (nem zerado, nem ponteiro) desse tipo: make(map[string]int) está pronto para uso, enquanto new(map[string]int) é um ponteiro para um map nil.
Posso devolver um ponteiro para uma variável local em Go?
Sim. Diferente de C, é seguro. A escape analysis do compilador vê que a variável sobrevive à função e a aloca no heap. func newInt() *int { x := 5; return &x } é Go correto.
O que causa "invalid memory address or nil pointer dereference" em Go?
Ler ou escrever por meio de um ponteiro nil: uma variável *T que nunca recebeu valor, uma busca de chave ausente em um map[K]*V ou uma função que devolveu nil junto com um erro que você não verificou. Verifique se é nil, ou inicialize o ponteiro antes de usar.
Go tem aritmética de ponteiros?
Não. Você não pode somar a um ponteiro nem indexar memória por meio dele. O pacote unsafe permite isso em código de baixo nível, sem garantia de segurança nenhuma. Código Go normal usa slices para memória contígua.