Ponteiros são um dos tópicos que mais intimidam iniciantes vindos de linguagens como Python, JavaScript ou Java — onde o programador raramente precisa pensar em endereços de memória. Em C e C++, ponteiros são poderosos mas perigosos: aritmética de ponteiros, ponteiros soltos e corrupção de memória são problemas reais.
Go encontra um meio-termo deliberado. Ponteiros existem e são necessários, mas a aritmética de ponteiros não é permitida, e o garbage collector cuida da memória automaticamente. O resultado é um sistema de ponteiros seguro e útil, sem as armadilhas clássicas do C.
O que é um ponteiro
Toda variável em um programa ocupa um espaço na memória, e esse espaço tem um endereço — um número que identifica sua localização. Um ponteiro é simplesmente uma variável que armazena esse endereço em vez de um valor direto.
Em Go, o tipo de um ponteiro para int é escrito como *int. O tipo de um ponteiro para string é *string. A regra é sempre *Tipo.
Dois operadores são fundamentais para trabalhar com ponteiros:
&— operador de endereço: obtém o endereço de uma variável*— operador de desreferência: acessa o valor no endereço armazenado
package main
import "fmt"
func main() {
x := 42
p := &x // p é um ponteiro para x; seu tipo é *int
fmt.Println(x) // 42 — valor de x
fmt.Println(p) // 0xc000... — endereço de x
fmt.Println(*p) // 42 — valor no endereço apontado por p
*p = 100 // modifica x através do ponteiro
fmt.Println(x) // 100
}
Alterar *p altera x diretamente, pois ambos apontam para o mesmo espaço na memória.
O valor zero de um ponteiro
O valor zero de qualquer tipo ponteiro em Go é nil. Um ponteiro nil não aponta para nenhum endereço válido. Tentar desreferenciar um ponteiro nil causa um pânico em tempo de execução:
var p *int
fmt.Println(p) // <nil>
fmt.Println(*p) // PANIC: runtime error: invalid memory address or nil pointer dereference
Sempre verifique se um ponteiro é nil antes de desreferenciá-lo quando sua origem não é garantida:
func imprimirValor(p *int) {
if p == nil {
fmt.Println("ponteiro nulo, nada a imprimir")
return
}
fmt.Println(*p)
}
Por que ponteiros existem: passagem por valor vs por referência
Em Go, todos os argumentos de função são passados por valor — o que significa que a função recebe uma cópia do argumento, não o original. Modificações dentro da função não afetam a variável original:
package main
import "fmt"
func tentarDobrar(n int) {
n = n * 2 // modifica apenas a cópia local
}
func main() {
x := 10
tentarDobrar(x)
fmt.Println(x) // 10 — x não foi alterado
}
Para que a função modifique o valor original, é necessário passar um ponteiro:
func dobrar(n *int) {
*n = *n * 2 // modifica o valor no endereço recebido
}
func main() {
x := 10
dobrar(&x)
fmt.Println(x) // 20 — x foi modificado
}
Ponteiros com structs
O uso mais comum de ponteiros em Go é com structs. Passar uma struct grande por valor copia todos os seus campos — o que pode ser custoso. Passar um ponteiro para a struct é mais eficiente e permite que a função modifique os campos originais:
package main
import "fmt"
type Retangulo struct {
Largura float64
Altura float64
}
func escalar(r *Retangulo, fator float64) {
r.Largura *= fator
r.Altura *= fator
}
func area(r *Retangulo) float64 {
return r.Largura * r.Altura
}
func main() {
r := Retangulo{Largura: 5.0, Altura: 3.0}
fmt.Println(area(&r)) // 15
escalar(&r, 2.0)
fmt.Println(area(&r)) // 60
}
Go oferece uma conveniência importante: ao acessar campos de uma struct através de um ponteiro, não é necessário desreferenciar manualmente. Em vez de escrever (*r).Largura, Go permite simplesmente r.Largura — o compilador faz a desreferência automaticamente.
Criando ponteiros com new
Além do operador &, Go oferece a função embutida new para alocar memória para um tipo e retornar um ponteiro para ela. O valor alocado é inicializado com o valor zero do tipo:
p := new(int) // aloca um int zerado, retorna *int
fmt.Println(*p) // 0
*p = 99
fmt.Println(*p) // 99
Na prática, new é menos comum do que o uso de & com um literal de struct. As duas formas abaixo são equivalentes:
// Com new
r1 := new(Retangulo)
r1.Largura = 5.0
// Com literal e &
r2 := &Retangulo{Largura: 5.0}
A segunda forma é preferida pela comunidade Go por ser mais explícita sobre o valor inicial.
Ponteiros e interfaces
Um padrão importante que aparecerá com frequência nos módulos seguintes é a distinção entre implementar uma interface com um receiver de valor versus um receiver de ponteiro. Por ora, é suficiente saber que métodos definidos com receiver de ponteiro só são acessíveis quando o valor é endereçável — ou seja, quando se tem um ponteiro para ele.
Esse tópico será explorado em detalhe no artigo sobre métodos e interfaces.
Quando usar ponteiros
A decisão de usar ou não ponteiros segue algumas diretrizes práticas que a comunidade Go consolidou ao longo dos anos.
Use ponteiros quando:
A função precisa modificar o valor original do argumento. Structs grandes são passadas como argumento com frequência e o custo de cópia importa. O tipo representa um recurso com identidade única — como uma conexão de banco de dados, um arquivo aberto ou um mutex — onde cópias não fazem sentido semântico. O valor pode ser nil para representar ausência.
Prefira valores quando:
O tipo é pequeno — inteiros, floats, bools, structs com poucos campos simples. A função não precisa modificar o argumento. O código fica mais simples e legível sem ponteiros. Tipos como time.Time, net/url.URL e a maioria dos tipos da biblioteca padrão são usados por valor.
// Prefer por valor — tipo pequeno, sem necessidade de modificação
func formatarNome(nome string) string {
return strings.Title(nome)
}
// Preferir por ponteiro — struct grande, função modifica o estado
func (s *Servidor) iniciar(porta int) error {
s.porta = porta
s.ativo = true
return s.escutar()
}
O que Go não permite: aritmética de ponteiros
Em C, é possível incrementar um ponteiro para navegar por posições consecutivas na memória. Em Go, isso é proibido:
x := 10
p := &x
p++ // erro de compilação: invalid operation: p++ (non-numeric type *int)
Essa restrição elimina uma classe inteira de bugs de memória. O pacote unsafe da biblioteca padrão oferece operações de ponteiro de baixo nível para casos extremamente específicos, mas seu nome é um aviso explícito — seu uso em código de aplicação comum é fortemente desencorajado.
Exemplo prático: lista encadeada simples
Para consolidar o conceito, abaixo está uma implementação simples de lista encadeada — uma estrutura de dados que depende fundamentalmente de ponteiros para existir:
package main
import "fmt"
type No struct {
Valor int
Proximo *No
}
func imprimirLista(inicio *No) {
for n := inicio; n != nil; n = n.Proximo {
fmt.Printf("%d ", n.Valor)
}
fmt.Println()
}
func main() {
terceiro := &No{Valor: 30, Proximo: nil}
segundo := &No{Valor: 20, Proximo: terceiro}
primeiro := &No{Valor: 10, Proximo: segundo}
imprimirLista(primeiro) // 10 20 30
}
Cada nó armazena um ponteiro para o próximo nó. O último nó aponta para nil, sinalizando o fim da lista. Esse padrão de *No dentro da própria struct No é possível porque o Go sabe que um ponteiro tem tamanho fixo, independentemente do tipo para o qual aponta.
Resumo do que foi coberto
Este artigo apresentou o modelo de ponteiros do Go: os operadores & e *, o valor zero nil, a diferença entre passagem por valor e por ponteiro, o uso com structs, a função new e as diretrizes práticas para decidir quando usar ponteiros. A ausência de aritmética de ponteiros foi destacada como uma escolha de segurança consciente da linguagem.
Referências e leituras complementares
-
Especificação da linguagem Go — Pointer types — Definição formal do tipo ponteiro. https://go.dev/ref/spec#Pointer_types
-
A Tour of Go — Pointers — Introdução interativa ao sistema de ponteiros. https://go.dev/tour/moretypes/1
-
Go by Example: Pointers — Exemplos práticos comentados. https://gobyexample.com/pointers
-
Effective Go — Allocation with new — Discussão sobre new e make na alocação de memória. https://go.dev/doc/effective_go#allocation_new
-
Understanding Pointers in Go — DigitalOcean — Tutorial detalhado com diagramas de memória. https://www.digitalocean.com/community/tutorials/understanding-pointers-in-go
-
Go Data Structures — Russ Cox — Artigo técnico sobre como Go representa tipos na memória. https://research.swtch.com/godata