Como Adiar a Execução de Funções com defer em Go: Um Guia Visual e Prático
Aprenda o defer do Go de forma visual: a pilha em que o último a entrar sai primeiro, quando as chamadas adiadas rodam de verdade, argumentos avaliados na hora, fechar arquivos e destravar mutexes, recuperar de pânicos, mudar o resultado, o os.Exit, um benchmark real e exercícios.

Nesta página
O Go roda os seus comandos em ordem, de cima para baixo. Mas algum trabalho pertence ao fim: fechar um arquivo que você abriu, destravar uma trava que você pegou, registrar que uma função terminou. A palavra-chave defer ("adiar") do Go deixa você escrever essa limpeza logo ao lado da preparação e fazê-la rodar automaticamente quando a função retornar, seja como for que ela retorne.
Neste guia você vai ver a ordem em que as chamadas adiadas rodam, exatamente quando elas rodam, um detalhe surpreendente sobre os argumentos delas e os padrões do mundo real: fechar arquivos, destravar mutexes, recuperar de pânicos e até mudar o resultado de uma função. Você também vai ver o único jeito de pular as chamadas adiadas, e um benchmark do quanto o defer custa. Exercícios fecham o artigo.
Todas as saídas abaixo vêm de execuções reais com o Go 1.22 no Windows.
Adiando uma chamada para depois
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("Oi")
7 fmt.Println("Tudo bem?")
8}Oi
Tudo bem?Agora coloque defer na frente da primeira chamada:
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("Oi")
7 fmt.Println("Tudo bem?")
8}Tudo bem?
OiO defer não roda a chamada agora. Ele a agenda para rodar quando a função em volta retornar, aqui quando a main terminar.
O último a entrar é o primeiro a sair
Adie várias chamadas e elas rodam na ordem inversa:
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("1")
7 defer fmt.Println("2")
8 defer fmt.Println("3")
9 fmt.Println("a main está quase retornando")
10}1a main está quase retornando
23
32
41
Cada função tem a sua própria pilha de defers (stack). Cada defer empilha uma chamada no topo; quando a função retorna, o Go as tira do topo: o último a entrar é o primeiro a sair (last-in, first-out, LIFO). É exatamente a ordem de que a limpeza precisa: se você abre um banco de dados e depois um arquivo, quer fechar o arquivo primeiro e o banco por último.
Quando exatamente uma chamada adiada roda?
Quando a função retorna, e não quando o bloco ou o laço atual termina:
1package main
2
3import "fmt"
4
5func ajudante() {
6 defer fmt.Println("ajudante: chamada adiada")
7 fmt.Println("ajudante: trabalhando")
8}
9
10func main() {
11 fmt.Println("main: antes do ajudante")
12 ajudante()
13 fmt.Println("main: depois do ajudante")
14
15 for passo := 1; passo <= 3; passo++ {
16 defer fmt.Println("defer do laço", passo)
17 }
18 fmt.Println("main: laço terminou")
19}1main: antes do ajudante
2ajudante: trabalhando
3ajudante: chamada adiada
4main: depois do ajudante
5main: laço terminou
6defer do laço 3
7defer do laço 2
8defer do laço 1
A chamada adiada do ajudante rodou assim que o ajudante retornou. Mas as três chamadas adiadas dentro do laço se acumularam até a main retornar. Isso importa quando um laço abre muitos arquivos: adiar o Close dentro do laço mantém todos os arquivos abertos até a função inteira terminar. Mova o corpo do laço para uma função própria, e cada Close adiado roda no fim de cada volta.
Os argumentos são avaliados na hora
Isto surpreende quase todo mundo:
1package main
2
3import "fmt"
4
5func main() {
6 contagem := 1
7 defer fmt.Println("adiada com o valor:", contagem)
8
9 defer func() {
10 fmt.Println("a closure adiada vê:", contagem)
11 }()
12
13 contagem = 99
14 fmt.Println("agora a contagem é", contagem)
15}agora a contagem é 99
a closure adiada vê: 99
adiada com o valor: 1
A chamada acontece depois, mas os argumentos dela são avaliados na linha do defer, então a contagem foi capturada como 1. Uma closure adiada (uma func() { … }() anônima) não tem argumentos: ela lê a contagem quando finalmente roda, e vê 99. Escolha o comportamento de que você precisa.
Uso real 1: fechar arquivos
O padrão clássico: abra algo e imediatamente adie o fechamento:
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8func escreverNotas(nomeDoArquivo string) error {
9 arquivo, err := os.Create(nomeDoArquivo)
10 if err != nil {
11 return err
12 }
13 defer arquivo.Close()
14
15 fmt.Fprintln(arquivo, "Comprar leite")
16 fmt.Fprintln(arquivo, "Ligar para a Ana")
17 return nil
18}
19
20func main() {
21 if err := escreverNotas("notas.txt"); err != nil {
22 fmt.Println("erro:", err)
23 return
24 }
25 defer os.Remove("notas.txt")
26
27 conteudo, err := os.ReadFile("notas.txt")
28 if err != nil {
29 fmt.Println("erro:", err)
30 return
31 }
32 fmt.Print(string(conteudo))
33}Comprar leite
Ligar para a AnaEscrever defer arquivo.Close() logo depois de um os.Create bem-sucedido quer dizer que você não tem como esquecer, mesmo que a função ganhe mais returns depois. (err é o nome convencional do Go para um valor de erro; conferir if err != nil depois de cada chamada que pode falhar é o padrão do Go.) O defer os.Remove apaga o arquivo de exemplo quando a main termina.
Uso real 2: destravar um mutex
Quando várias goroutines (as "threads" leves do Go) dividem dados, um mutex garante que só uma mexa neles por vez. O defer garante o destravamento:
1package main
2
3import (
4 "fmt"
5 "sync"
6)
7
8type Contador struct {
9 trava sync.Mutex
10 valor int
11}
12
13func (contador *Contador) Incrementar() {
14 contador.trava.Lock()
15 defer contador.trava.Unlock()
16 contador.valor++
17}
18
19func main() {
20 contador := &Contador{}
21 var grupo sync.WaitGroup
22 for trabalhador := 0; trabalhador < 100; trabalhador++ {
23 grupo.Add(1)
24 go func() {
25 defer grupo.Done()
26 contador.Incrementar()
27 }()
28 }
29 grupo.Wait()
30 fmt.Println("valor final:", contador.valor)
31}valor final: 100100 goroutines somam 1 cada uma, e o resultado é sempre 100. Se o Incrementar retornasse antes ou entrasse em pânico sem destravar, todas as outras goroutines esperariam para sempre. Com o defer, o destravamento sempre acontece. (O defer grupo.Done() é a mesma ideia: avisa o WaitGroup que esta goroutine terminou, aconteça o que acontecer.)
Uso real 3: recuperar de um pânico
Um pânico (panic) é a parada de emergência do Go (como o erro "index out of range" que você talvez já tenha visto). As chamadas adiadas continuam rodando durante um pânico, e uma função adiada pode chamar recover() para parar o pânico e transformá-lo num erro comum:
1package main
2
3import "fmt"
4
5func elementoSeguro(numeros []int, indice int) (valor int, err error) {
6 defer func() {
7 if problema := recover(); problema != nil {
8 err = fmt.Errorf("recuperado: %v", problema)
9 }
10 }()
11 return numeros[indice], nil
12}
13
14func main() {
15 numeros := []int{10, 20, 30}
16 fmt.Println(elementoSeguro(numeros, 1))
17 fmt.Println(elementoSeguro(numeros, 5))
18 fmt.Println("o programa continua")
19}20 <nil>
0 recuperado: runtime error: index out of range [5] with length 3
o programa continuaO recover() só funciona dentro de uma função adiada. Use com moderação, em fronteiras como o tratador de pedidos de um servidor web, para que um pedido ruim não derrube o servidor inteiro. Erros comuns devem ser devolvidos como valores error.
Uso real 4: mudar o resultado
Olhe de novo o elementoSeguro: os resultados dele têm nome (valor int, err error), e a função adiada mudou o err depois que o comando return já o tinha definido. Uma função adiada roda depois que o return define os resultados, mas antes de quem chamou recebê-los, então ela pode ajustá-los:
1package main
2
3import "fmt"
4
5func dobro(numero int) (resultado int) {
6 defer func() {
7 resultado *= 2
8 }()
9 return numero + 1
10}
11
12func main() {
13 fmt.Println(dobro(4))
14}10O return numero + 1 põe 5 no resultado, e depois a função adiada o dobra para 10. Útil para acrescentar contexto a erros; confuso se exagerado.
A única coisa que pula os defers: os.Exit
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8func main() {
9 defer fmt.Println("isto nunca é impresso")
10 fmt.Println("saindo")
11 os.Exit(0)
12}saindoO os.Exit termina o programa na hora: as chamadas adiadas não rodam, então arquivos podem não ser gravados e a limpeza é pulada. (Um pânico que ninguém recupera roda as chamadas adiadas antes; o os.Exit, não.) Na main, prefira retornar normalmente, ou use o os.Exit só depois de terminar a limpeza.
Quanto o defer custa?
Versões antigas do Go deixavam o defer bem mais lento, e por isso parte do código o evitava. Medimos travar e destravar um mutex com e sem defer no Go 1.22:

| O quê | Tempo por chamada |
|---|---|
| Travar e destravar direto | 14,62 ns |
Travar e adiar o destravamento com defer | 15,16 ns |
| Uma closure chamada dentro de um laço | 1,91 ns |
| A mesma closure adiada dentro de um laço | 26,29 ns |
Desde o Go 1.14, o compilador embute (open-codes) as chamadas adiadas simples, copiando-as para cada lugar em que a função retorna, então um defer comum custa cerca de meio nanossegundo (a diferença mudava de uma execução para outra). Só um defer dentro de um laço (em que o número de chamadas adiadas não é conhecido de antemão) pega o caminho mais lento: cerca de 24 nanossegundos por chamada adiada, comparado com chamar a mesma closure direto. Use o defer à vontade para a limpeza.
Sua vez: pratique
Tente fazer estes antes de espiar as respostas. Rode cada um com go run ..
Exercício 1: Adivinhe a saída (aquecimento). O que isto imprime?
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("A")
7 fmt.Println("B")
8 defer fmt.Println("C")
9 fmt.Println("D")
10}Exercício 2: Contagem regressiva. Escreva um laço de 1 a 5 que adie a impressão de cada número, seguido de uma linha que imprime Contando…, para a saída mostrar Contando… e depois 5, 4, 3, 2, 1.
Exercício 3: Rastreie uma função. Escreva um ajudante rastrear(nome string) func() que imprime entrou nome quando é chamado e devolve uma função que imprime saiu nome, para que defer rastrear("processar")() na primeira linha de uma função registre o começo e o fim dela.
Exercício 4: Adivinhe os argumentos. O que isto imprime, e por quê?
1package main
2
3import "fmt"
4
5func main() {
6 mensagem := "primeira"
7 defer fmt.Println(mensagem)
8 mensagem = "segunda"
9 defer func() { fmt.Println(mensagem) }()
10 mensagem = "terceira"
11}Exercício 5: Divisão segura (desafio). Escreva divisaoSegura(numerador, denominador int) (resultado int, err error), que recupera do pânico "integer divide by zero" (divisão inteira por zero) e o devolve como erro.
Quiz rápido
- Em que ordem três chamadas adiadas rodam?
- Quando roda uma chamada adiada dentro de um laço?
- Quando os argumentos de uma chamada adiada são avaliados?
- As chamadas adiadas rodam durante um pânico? E durante um
os.Exit? - Por que colocar
defer arquivo.Close()logo depois de abrir um arquivo?
Respostas
Exercício 1: B e D são impressos enquanto a main roda; depois as chamadas adiadas rodam na ordem inversa, o C antes do A:
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("A")
7 fmt.Println("B")
8 defer fmt.Println("C")
9 fmt.Println("D")
10}1B
2D
3C
4AExercício 2: As impressões adiadas se acumulam e rodam ao contrário depois do laço:
1package main
2
3import "fmt"
4
5func main() {
6 for numero := 1; numero <= 5; numero++ {
7 defer fmt.Println(numero)
8 }
9 fmt.Println("Contando…")
10}1Contando…
25
34
43
52
61Exercício 3: O rastrear("processar") roda na hora (imprimindo entrou), e a função que ele devolve é a que fica adiada:
1package main
2
3import "fmt"
4
5func rastrear(nome string) func() {
6 fmt.Println("entrou", nome)
7 return func() {
8 fmt.Println("saiu", nome)
9 }
10}
11
12func processar() {
13 defer rastrear("processar")()
14 fmt.Println("processando…")
15}
16
17func main() {
18 processar()
19}entrou processar
processando…
saiu processarRepare nos dois pares de parênteses: defer rastrear("processar")() adia a função devolvida. Escrever defer rastrear("processar") adiaria a chamada inteira do rastrear, imprimindo entrou no fim.
Exercício 4: A primeira chamada adiada capturou "primeira" como argumento; a closure lê a mensagem quando roda, depois de ela virar "terceira". A ordem inversa põe a closure primeiro:
1package main
2
3import "fmt"
4
5func main() {
6 mensagem := "primeira"
7 defer fmt.Println(mensagem)
8 mensagem = "segunda"
9 defer func() { fmt.Println(mensagem) }()
10 mensagem = "terceira"
11}terceira
primeiraExercício 5: Um recover adiado transforma o pânico num erro por meio do resultado com nome:
1package main
2
3import "fmt"
4
5func divisaoSegura(numerador, denominador int) (resultado int, err error) {
6 defer func() {
7 if problema := recover(); problema != nil {
8 err = fmt.Errorf("não dá para dividir: %v", problema)
9 }
10 }()
11 return numerador / denominador, nil
12}
13
14func main() {
15 fmt.Println(divisaoSegura(10, 2))
16 fmt.Println(divisaoSegura(10, 0))
17}5 <nil>
0 não dá para dividir: runtime error: integer divide by zero(Em código de verdade você simplesmente conferiria if denominador == 0 antes; este exercício é para ver o recover funcionando.)
Quiz: (1) Ao contrário: o último adiado é o primeiro a rodar. (2) Quando a função inteira em volta retorna, e não no fim de cada volta. (3) Na linha do defer; só a chamada em si espera. (4) Sim durante um pânico (é assim que o recover funciona); não durante o os.Exit, que termina o programa na hora. (5) Para o fechamento não poder ser esquecido, seja qual for o return que a função tomar depois.
Cola rápida
| Eu quero… | Escreva |
|---|---|
| Rodar uma chamada quando a função retornar | defer fmt.Println("pronto") |
| Fechar um arquivo | arquivo, err := os.Open(nome) … defer arquivo.Close() |
| Destravar um mutex | trava.Lock() e depois defer trava.Unlock() |
| Marcar uma goroutine como terminada | defer grupo.Done() |
| Ler as variáveis no fim, e não agora | defer func() { … }() |
| Transformar um pânico num erro | defer func() { if problema := recover(); problema != nil { err = … } }() |
| Mudar um resultado com nome | defer func() { resultado *= 2 }() |
| Rastrear uma função | defer rastrear("nome")() |
| Sair sem rodar os defers | os.Exit(codigo) (com cuidado) |
Resumindo
O defer mantém a preparação e a limpeza lado a lado:
- Uma chamada adiada roda quando a função em volta retorna, com o último a entrar saindo primeiro.
- Ela pertence à função, e não ao bloco: defers dentro de um laço se acumulam até a função terminar.
- Os argumentos são avaliados na linha do
defer; uma closure adiada lê as variáveis quando roda. - Usos clássicos: fechar arquivos, destravar mutexes,
WaitGroup.Doneerecoverpara transformar um pânico num erro. - Funções adiadas podem mudar resultados com nome, porque rodam depois que o
returnos define. - O
os.Exitpula as chamadas adiadas; um pânico, não. - Quase não custa nada: 15,16 ns contra 14,62 ns para destravar um mutex no nosso benchmark; só defers dentro de laços custam mais (cerca de 24 ns cada).
Voltando pela série: Slices de Duas Dimensões em Go, O Laço For em Go e Seu Primeiro Programa em Go. A seguir: ponteiros em Go.
Leia a seguir

Como Tratar Erros em Go: Um Guia Prático
Aprenda a tratar erros em Go com exemplos executáveis: retorne um erro, confira o resultado, acrescente contexto com %w e reconheça um arquivo ausente com errors.Is.

O Que É a Linguagem de Programação Rust? Um Guia Simples da Linguagem Rápida Que Não Deixa Você Travar
Um guia simples e sem jargões sobre o Rust, a linguagem tão rápida quanto o C que impede famílias inteiras de erros de memória antes de o programa rodar. Entenda a ideia de posse, por que gigantes da tecnologia o adotam e onde ele é usado.

O Que É a Linguagem de Programação Go? Um Guia Simples da Linguagem da Nuvem
Um guia simples e sem jargões sobre o Go, a linguagem simples e rápida que o Google criou para a nuvem. Entenda o que são goroutines, por que um programa Go é um arquivo só e como o Go passou a mover o Docker, o Kubernetes e boa parte da internet.
Comentários
Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.