O que é Pointer em Go?

Um pointer (ponteiro) em Go é uma variável que armazena o endereço de memória de outra variável. Em vez de conter o valor diretamente, um ponteiro “aponta” para a localização onde o valor está armazenado na memória. Isso permite que funções modifiquem dados originais sem copiar grandes estruturas, reduzindo uso de memória e possibilitando compartilhamento eficiente de dados.

Go oferece ponteiros de forma segura e controlada — diferente de C/C++, não existe aritmética de ponteiros em Go. Você não pode incrementar um ponteiro para “andar” pela memória, nem converter ponteiros para inteiros. Essa restrição elimina classes inteiras de bugs (buffer overflows, dangling pointers, segfaults) enquanto mantém os benefícios de performance que ponteiros oferecem.

O tipo de um ponteiro é escrito como *T, onde T é o tipo do valor apontado. Os dois operadores fundamentais são:

  • & (address-of): obtém o endereço de memória de uma variável
  • * (dereference): acessa o valor armazenado no endereço apontado

Operadores & e *

Operador & — Obtendo o endereço

x := 42
p := &x // p é do tipo *int, contém o endereço de x

fmt.Println(x)  // 42 (o valor)
fmt.Println(p)  // 0xc000012090 (o endereço — varia a cada execução)
fmt.Println(&x) // 0xc000012090 (mesmo endereço)

Operador * — Acessando o valor (dereferencing)

x := 42
p := &x

fmt.Println(*p) // 42 — lê o valor no endereço

*p = 100        // modifica o valor no endereço
fmt.Println(x)  // 100 — x foi modificado via ponteiro!

Criando ponteiros com new()

// new() aloca memória e retorna ponteiro para zero value
p := new(int)    // *int apontando para 0
*p = 42

// Equivalente a:
var x int
p2 := &x

Na prática, new() é pouco usado em Go idiomático. A forma mais comum é usar & com literais:

type Config struct {
    Host string
    Port int
}

// Criar ponteiro para struct diretamente
cfg := &Config{Host: "localhost", Port: 8080}

Pointer receivers vs value receivers

Uma das decisões mais importantes ao definir métodos em Go é escolher entre receiver por valor ou por ponteiro:

type Contador struct {
    valor int
}

// Value receiver — trabalha com cópia
func (c Contador) Atual() int {
    return c.valor
}

// Pointer receiver — modifica o original
func (c *Contador) Incrementar() {
    c.valor++
}

func main() {
    cont := Contador{valor: 0}
    cont.Incrementar() // Go automaticamente pega &cont
    fmt.Println(cont.Atual()) // 1
}

Quando usar pointer receiver

Use pointer receiver quando:

  1. O método modifica o receiver — regra número um
  2. A struct é grande — evita cópia na chamada do método
  3. Consistência — se algum método precisa de pointer receiver, todos devem usar

Quando usar value receiver

Use value receiver quando:

  1. O tipo é pequeno e imutável (como time.Time)
  2. O método apenas lê dados, nunca modifica
  3. O tipo implementa uma interface onde semântica de valor faz sentido

Na dúvida, use pointer receiver — é a escolha mais segura e idiomática para structs.

Nil pointers

O zero value de qualquer tipo ponteiro é nil:

var p *int
fmt.Println(p)    // <nil>
fmt.Println(p == nil) // true

// Dereferencing nil pointer = PANIC!
// fmt.Println(*p) // panic: runtime error: invalid memory address

Verificando nil antes de usar

func processarUsuario(u *Usuario) error {
    if u == nil {
        return errors.New("usuário não pode ser nil")
    }
    // Seguro usar u aqui
    fmt.Println(u.Nome)
    return nil
}

Verificar nil é especialmente importante ao trabalhar com tratamento de erros e valores opcionais.

Ponteiros para structs

Structs e ponteiros andam juntos em Go. O acesso a campos de struct via ponteiro é simplificado — Go faz o dereferencing automaticamente:

type Pessoa struct {
    Nome  string
    Idade int
}

p := &Pessoa{Nome: "Ana", Idade: 30}

// Estas duas formas são equivalentes:
fmt.Println((*p).Nome) // "Ana" — dereferencing explícito
fmt.Println(p.Nome)    // "Ana" — Go faz automaticamente

Esse açúcar sintático torna o trabalho com ponteiros para structs natural e limpo.

Structs com campos ponteiro

type Pedido struct {
    ID        int
    Cliente   *Cliente   // opcional — pode ser nil
    Desconto  *float64   // ponteiro permite distinguir "0%" de "não definido"
}

// Usando ponteiro para representar valor opcional
func criarPedido(desconto *float64) Pedido {
    return Pedido{
        ID:       1,
        Desconto: desconto,
    }
}

// Sem desconto
p1 := criarPedido(nil)

// Com desconto de 10%
d := 0.10
p2 := criarPedido(&d)

Esse padrão é comum em APIs e ORMs onde nil indica “campo não fornecido” vs zero value que indica “valor explicitamente definido como zero”.

Quando usar ponteiros

Use ponteiros quando

  • Precisa modificar o valor original em uma função
  • A struct é grande (> 64 bytes como regra geral)
  • Precisa representar “ausência de valor” com nil
  • Implementa interfaces que requerem mutação
  • Está trabalhando com shared state entre goroutines

Evite ponteiros quando

  • O tipo é pequeno e imutável (int, float, bool, small structs)
  • Quer garantir imutabilidade — valor por cópia é mais seguro
  • Está lidando com slices e maps — já são referências internamente
  • Não precisa de nil semantics
// Slices e maps já contêm ponteiros internamente
// Passar por valor já é "por referência" para o backing data
func modificarSlice(s []int) {
    s[0] = 999 // modifica o original!
}

func modificarMap(m map[string]int) {
    m["novo"] = 42 // modifica o original!
}

Ponteiros e escape analysis

O compilador Go decide automaticamente se uma variável vai para o stack (rápido, automático) ou para o heap (requer garbage collection):

// Geralmente fica no stack — curto tempo de vida
func somar(a, b int) int {
    resultado := a + b
    return resultado
}

// Escapa para o heap — ponteiro sobrevive à função
func criarConfig() *Config {
    cfg := Config{Host: "localhost", Port: 8080}
    return &cfg // &cfg escapa — precisa viver além da função
}

Você pode verificar decisões de escape analysis com:

go build -gcflags="-m" ./...

Para mais detalhes sobre otimização de performance em Go, veja o tutorial de profiling e o guia de PGO.

Ponteiros em interfaces

Um detalhe sutil: um *T implementa todas as interfaces que T implementa, mas o inverso não é verdade:

type Stringer interface {
    String() string
}

type Animal struct {
    Nome string
}

// Pointer receiver
func (a *Animal) String() string {
    return a.Nome
}

// Funciona — *Animal implementa Stringer
var s Stringer = &Animal{Nome: "Rex"}

// NÃO compila — Animal (valor) não implementa Stringer
// var s2 Stringer = Animal{Nome: "Rex"}

Entender essa relação é fundamental ao trabalhar com interfaces em Go e injeção de dependência.

Boas práticas com ponteiros

  1. Verifique nil antes de usar qualquer ponteiro recebido como parâmetro
  2. Prefira value receivers para tipos pequenos e imutáveis
  3. Seja consistente — se um método usa pointer receiver, todos devem usar
  4. Não retorne ponteiros para variáveis locais de tipos básicos sem razão
  5. Use ponteiros para optional fields em structs de configuração/API
  6. Evite pointer para slice/map — já são referências internamente

Para aprofundar, explore o tratamento de erros em Go e os padrões de concorrência.

Perguntas frequentes (FAQ)

Go tem aritmética de ponteiros como C?

Não. Go deliberadamente não permite aritmética de ponteiros. Você não pode somar, subtrair ou comparar ponteiros com operadores aritméticos (exceto == e !=). Isso elimina classes inteiras de bugs de memória enquanto mantém os benefícios de referência indireta que ponteiros oferecem.

Quando usar ponteiro vs valor como parâmetro de função?

Use ponteiro quando precisa modificar o valor original ou quando a struct é grande (> 64 bytes). Use valor para tipos pequenos e imutáveis (int, string curta, small structs). Lembre que slices e maps já são referências internamente, então não precisam de ponteiro na passagem.

O que acontece ao dereferenciar um nil pointer?

O programa entra em panic com a mensagem runtime error: invalid memory address or nil pointer dereference. Sempre verifique se um ponteiro é nil antes de usá-lo, especialmente em parâmetros de função e campos opcionais de structs. Use error handling para tratar esses cenários graciosamente.

Ponteiros afetam a performance em Go?

Sim, de formas nem sempre óbvias. Ponteiros podem evitar cópias de structs grandes (positivo), mas valores no heap exigem garbage collection (negativo). Para tipos pequenos, passar por valor é geralmente mais rápido por melhor localidade de cache. Use benchmarks e o profiler para decisões baseadas em dados.