Menu

Ponteiros em Golang: & e *, new, nil e quando usar

Um ponteiro guarda o endereço de um valor. Veja & e *, new, ponteiros para structs, por que é seguro devolver um ponteiro para uma variável local, quando usar ponteiros e o panic de desreferência de ponteiro nil.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

& 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 um map[string]*T para uma chave ausente, que devolve nil
  • ignorar um erro: f, _ := os.Open(path) deixa f nil 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 (*int diferencia "ausente" de 0)

Não use um ponteiro quando:

  • o valor é pequeno e só de leitura (time.Time, um Point)
  • o tipo já se comporta como referência: slices, maps, channels, funções e interfaces raramente precisam de * na frente. Um *[]int ou *map[string]int quase 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 == nil onde nil for possível.
  • Ponteiro para o valor do range. &v aponta 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 um append. Se o append realocar, 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR