---
title: "Ponteiros em Go: Entenda de Vez Valor, Referência e Métodos"
url: "https://golang.com.br/blog/ponteiros-go-guia-completo/"
markdown_url: "https://golang.com.br/blog/ponteiros-go-guia-completo.MD"
description: "Ponteiros em Go explicados sem mistério: \u0026 e *, valor vs referência, quando usar em structs e métodos, nil, goroutines, interfaces e as armadilhas mais comuns — com exemplos."
date: "2026-07-11"
author: "Golang Brasil"
---

# Ponteiros em Go: Entenda de Vez Valor, Referência e Métodos

Ponteiros em Go explicados sem mistério: & e *, valor vs referência, quando usar em structs e métodos, nil, goroutines, interfaces e as armadilhas mais comuns — com exemplos.


Ponteiros é o primeiro assunto que trava quem chega em Go vindo do Python, JavaScript ou Java. Nas linguagens gerenciadas, a memória "simplesmente funciona": você cria um objeto, passa ele pra frente e ninguém fala de endereço. Em Go, de repente aparecem `&`, `*`, `*Usuario`, `func (u *Usuario)` e a sensação é a de que voltou pra faculdade de C. A boa notícia: ponteiro em Go é **muito** mais simples e seguro que em C. Não há aritmética de ponteiros, não há `malloc`/`free` e o coletor de lixo cuida da memória. O que resta é um conceito pequeno que resolve três problemas práticos: mutar um valor, evitar cópia desnecessária e expressar "isto pode estar ausente".

Este guia cobre o suficiente para você parar de adivinhar entre `func (u Usuario)` e `func (u *Usuario)`, entender por que sua função não altera o slice da forma como esperava, e evitar os dois bugs clássicos de quem está começando. Se você ainda está destrinchando a base da linguagem, leia antes o guia de [Go para iniciantes](/aprenda/go-para-iniciantes/) e a [introdução a interfaces](/aprenda/golang-interfaces/); ponteiros fazem mais sentido depois que structs e métodos estão sólidos.

## O que é um ponteiro, sem mistério

Um ponteiro é o **endereço** de uma variável na memória. Em vez de guardar o valor `42`, você guarda "onde está o 42". Dois operadores resolvem 90% do uso:

```go
x := 42
p := &x        // p é *int (ponteiro para int) e vale o endereço de x
fmt.Println(p) // 0xc0000b2000 (um endereço, não 42)
fmt.Println(*p) // 42 (lê o valor apontado)
*p = 100       // escreve no endereço — agora x vale 100
fmt.Println(x)  // 100
```

`&` ("endereço de") e `*` ("valor em") são as duas faces da mesma moeda. `&x` pega o endereço; `*p` dereferencia (lê ou escreve o que está lá). O tipo `*int` se lê "ponteiro para int". O zero value de qualquer ponteiro é `nil` — um ponteiro que não aponta pra lugar nenhum.

## Valor vs referência: o exemplo que fixa

Go é **sempre** pass-by-value. Cada chamada de função copia os argumentos. Isso significa que mutar um parâmetro dentro da função não afeta quem chamou — a menos que você passe um endereço. Veja o exemplo clássico:

```go
package main

import "fmt"

// Não funciona: a, b e c são cópias locais.
func trocaErrado(a, b int) {
    a, b = b, a
}

// Funciona: a e b apontam para as variáveis do chamador.
func troca(a, b *int) {
    *a, *b = *b, *a
}

func main() {
    x, y := 1, 2
    trocaErrado(x, y)
    fmt.Println(x, y) // 1 2 — nada mudou

    troca(&x, &y)
    fmt.Println(x, y) // 2 1 — agora sim
}
```

`trocaErrado` recebe **cópias** de `x` e `y`; a troca acontece só dentro da função e some no return. `troca` recebe os **endereços**, então escrever em `*a` modifica a variável original. Esse é o motivo de existirem ponteiros: dar à função uma forma de alcançar o dado que está fora dela.

## A regra que mais engana: slices, maps e channels já são referências

Quem vem de outra linguagem tenta "otimizar" passando `*[]int` ou `*map[string]int` — e está errado. Em Go, **slices, maps e channels já carregam ponteiros internamente**. Passar o valor deles (não o ponteiro) já permite que a função muta o conteúdo:

```go
func appendTag(s []string) []string {
    return append(s, "go")
}

func setar(m map[string]int) {
    m["brasil"] = 1 // muta o map original — não precisa de *map
}

func main() {
    m := map[string]int{}
    setar(m)
    fmt.Println(m["brasil"]) // 1
}
```

A armadilha com slice é sutil: `append` pode (ou não) realocar o array interno. Se você fizer `append(s, x)` sem retornar, o chamador pode não ver o novo elemento. Por isso a assinatura idiomática é `func appendTag(s []string) []string` — devolva o slice. Não passe `*[]string`; isso é quase sempre erro de entendimento. Para fixar os tipos compostos de uma vez, o [cheatsheet de slices, maps e structs](/cheatsheet/slices-maps-structs/) é um bom atalho.

## Quando usar ponteiros (e quando não)

Use ponteiros quando:

- **Precisa mutar o original** (o caso do `troca` acima).
- **O struct é grande** e você quer evitar copiar dezenas de campos a cada chamada.
- **Quer expressar "ausência"** — um campo `*string` ou `*int` pode ser `nil`, enquanto um `string` vazio é ambíguo entre "não informado" e "vazio".
- **Implementa um método que altera o receiver**, como `func (c *Conta) Sacar(valor int)`.

**Não** use ponteiros quando:

- O tipo já é referência (slice, map, channel, function, interface).
- O struct é pequeno (até uns 5–8 campos primitivos). Copiar é barato, fica na cache, e evita pressão no garbage collector.
- A função só lê o valor. Receba por valor — fica claro que não há efeito colateral e o código fica mais fácil de raciocinar.
- Você quer otimizar sem medir. Ponteiros às vezes **pioram** a performance porque forçam alocação no heap (mais sobre isso adiante).

O instinto saudável em Go é o oposto do de C: comece **sem** ponteiro e adicione só quando aparece um motivo real. Código cheio de `*` sem necessidade é mais difícil de ler e mais lento.

## Structs e métodos: value receiver vs pointer receiver

A decisão `func (u Usuario)` vs `func (u *Usuario)` aparece em todo projeto. A regra prática:

```go
type Usuario struct {
    Nome  string
    Email string
}

// Value receiver: não muta, Usuario é pequeno, cópia é barata.
func (u Usuario) Saudacao() string {
    return "Olá, " + u.Nome
}

// Pointer receiver: muta o receiver original.
func (u *Usuario) DefinirEmail(email string) {
    u.Email = email
}
```

Use **pointer receiver** quando o método muta o receiver, quando o struct é grande, ou para manter consistência com outros métodos do tipo que já usam ponteiro (misturar os dois no mesmo tipo é fonte de confusão e de métodos "somehow" não aparecendo na interface). Use **value receiver** para structs pequenos e imutáveis, como `time.Time`. O guia de [dependency injection sem framework](/blog/dependency-injection-go-sem-framework/) mostra como esse padrão sustenta código testável: você depende de interfaces pequenas e os receivers concretos são injetados.

## nil, ponteiros e o pânico

Dereferenciar um ponteiro `nil` causa pânico em runtime — é uma das fontes mais comuns de `nil pointer dereference` em produção:

```go
var u *Usuario      // u é nil
fmt.Println(u.Nome) // panic: runtime error: invalid memory address
```

A defesa é sempre checar antes de usar quando o ponteiro pode ser `nil` (vindo de um mapa, de um JSON, de uma função que "talvez" retorne):

```go
if u != nil {
    fmt.Println(u.Nome)
}
```

Atenção à diferença entre tipos: um **map `nil`** permite leitura (devolve o zero value) mas **pânico ao escrever**; um **slice `nil`** funciona com `len`, `append` e range. Ponteiros `nil` pânicam em qualquer acesso. Para os erros típicos de runtime, a seção de [erros comuns em Go](/erros/) tem páginas dedicadas ao [nil pointer dereference](/erros/nil-pointer-dereference/) e às condições de corrida.

## new(T), &T{} e o mito do "perigoso como C"

Duas formas de obter um ponteiro:

```go
p1 := new(int)    // *int, valor 0
*p1 = 42

u := &Usuario{Nome: "Ana"} // *Usuario, idiomático
```

`new(T)` devolve `*T` apontando para um `T` zerado; raramente aparece em código real. `&T{...}` (composite literal com `&`) é a forma idiomática quando você quer inicializar já com valores. **Diferente de C**, retornar o endereço de uma variável local é totalmente seguro em Go:

```go
func novoContador() *int {
    c := 0
    return &c // seguro: escape analysis move c para o heap
}
```

O compilador detecta (via *escape analysis*) que `&c` escapa da função e aloca `c` no heap; o coletor de lixo libera quando ninguém mais usa. Não há dangling pointer.

## Ponteiros e interfaces: o nil trap clássico

A interface em Go é um par (tipo, valor). Uma interface só é `nil` quando **ambos** são nil. Devolver um ponteiro tipado `nil` dentro de uma interface produz uma interface **não-nil** que vale nil — bug sutil:

```go
type Repositorio interface{ Buscar(id int) *Usuario }

func (db *DB) Buscar(id int) *Usuario {
    return nil // *Usuario nil
}

var r Repositorio = &DB{}
u := r.Buscar(1)
if u == nil { // true aqui — ok, porque u é *Usuario
}

// Mas isto quebra:
var err error
err = retornaErroConcreto() // retorna (*MeuErro)(nil)
if err != nil {             // true! a interface não é nil
    // executa mesmo sem erro de verdade
}
```

A regra: devolva `nil` **concreto do tipo retornado** e deixe quem chama atribuir à interface, ou retorne a interface direto como `nil`. O guia de [interfaces em Go](/aprenda/golang-interfaces/) aprofunda esse comportamento, que é cobrado em [perguntas de entrevista](/aprenda/perguntas-entrevista-go/).

## Ponteiros entre goroutines: não compartilhe, comunique

Compartilhar um `*T` entre goroutines e ler/escrever sem sincronização é **data race** — comportamento indefinido, bugs que só aparecem sob carga:

```go
// DATA RACE: duas goroutines escrevendo no mesmo *int
func main() {
    n := 0
    go func() { n++ }()
    go func() { n++ }()
}
```

Rode sempre com `go test -race` ou `go run -race` para capturar isso. A filosofia do Go é *"don't communicate by sharing memory; share memory by communicating"* — passe o dado por um [channel](/blog/channels-go-comunicacao-goroutines-producao/) ou proteja com `sync.Mutex`/`sync/atomic`. Os [padrões de concorrência](/tutoriais/go-concurrency-patterns/) e o guia de [errgroup](/blog/errgroup-go-concorrencia-cancelamento-erros/) mostram como orquestrar goroutines com cancelamento e limite sem cair em race. Quem chega em [entrevistas técnicas de Go](/aprenda/perguntas-entrevista-go/) sem saber responder "por que não compartilhar ponteiros entre goroutines" perde pontos fáceis.

## Ponteiros e performance: quando copiar é mais barato

Surpresa comum: adicionar ponteiros "para evitar cópia" **piorou** a performance. Motivo: quando o compilador vê que um ponteiro escapa da função, aloca no heap, e cada heap allocation vira trabalho para o garbage collector. Uma struct pequena passada por valor fica na stack — zero GC.

A regra é medir, não adivinhar. Escreva um benchmark, rode com `-benchmem`, e só converta para ponteiro se a cópia realmente aparecer como gargalo. Para entrar nesse tipo de análise, o guia de [pprof em produção](/blog/pprof-go-producao/) e o de [`sync.Pool` para reutilização de objetos](/blog/sync-pool-go-reutilizacao-objetos-performance/) são os próximos passos naturais — e o [Effective Go em 2026](/blog/effective-go-2026/) resume o que da filosofia original ainda orienta essas decisões.

## Armadilhas comuns para revisar antes do commit

- **Ponteiro para variável de loop** (pré-Go 1.22): `for _, v := range xs { save(&v) }` salvava o mesmo endereço a cada iteração. A partir do Go 1.22, `v` é uma nova variável por iteração — o bug sumiu, mas vale saber se você mantém código antigo.
- **`*struct` com campo nil no meio**: `a.b.c` pânica se `a.b` for `nil`. Inicialize a cadeia ou valide por partes.
- **Confundir slice vazio com slice nil**: ambos têm `len == 0`, mas `json.Marshal` produz `[]` e `null` respectivamente. Para APIs, seja explícito com `make([]T, 0)`.
- **Retornar ponteiro para mutex**: `*sync.Mutex` compartilhado entre goroutines pode ser correto, mas geralmente é melhor embutir o valor e passar o ponteiro do struct inteiro.

## Checklist rápido

1. Pass-by-value sempre; ponteiro é a forma de mutar o original.
2. Slice, map, channel já são referência — não enrole com `*slice`.
3. Pointer receiver para mutar ou struct grande; seja consistente no tipo.
4. Cheque `nil` antes de dereferenciar quando o ponteiro vem de fora.
5. Nunca compartilhe ponteiro mutável entre goroutines sem sincronização.
6. Meça antes de "otimizar" com ponteiros; cópia na stack costuma ganhar.

Ponteiros em Go são menos do que parecem: dois operadores (`&` e `*`), uma decisão de receiver por tipo e disciplina com `nil` e concorrência. Quem internaliza isso para de brigar com a linguagem e passa a escrever código idiomático — o que é exatamente o que times Go no Brasil procuram. Para fechar o ciclo de fundamentos, combine este guia com o de [erros em Go](/aprenda/golang-erros/), o de [interfaces](/aprenda/golang-interfaces/) e a trilha completa no [hub de tutoriais](/aprenda/). Para quem está de olho no mercado, as [vagas de Go no Brasil](/vagas/) e o comparativo de [salários de desenvolvedor Go](/carreira/salarios-go-brasil/) mostram onde esses fundamentos viram oportunidade — e o portal <a href="https://eu.dev.br/vagas/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'eu.dev.br' })">eu.dev.br reúne vagas de tecnologia no Brasil</a> ajuda a comparar a demanda por Go com outras stacks lado a lado.
