panic, recover e defer
Esta aula aprofunda os mecanismos de controle de fluxo e gerenciamento de erros em Go: defer, panic e recover. Você aprenderá a usar defer para adiar execuções, a lidar com pânicos e a recuperá-los de forma segura, além de entender quando essas práticas são recomendadas.
Em Go, o tratamento de erros é explícito e baseado em valores de retorno. No entanto, a linguagem também oferece mecanismos para situações excepcionais e para garantir a limpeza de recursos: defer, panic e recover. Nesta aula, você vai dominar esses três mecanismos, entendendo sua ordem de execução, como usá-los corretamente e, principalmente, quando evitá-los.
O defer é uma ferramenta elegante para adiar a execução de uma função até o retorno da função atual, sendo útil para fechar recursos ou liberar locks. panic é usado para interromper o fluxo normal quando algo inesperado ocorre, e recover permite capturar esse pânico e retomar o controle. Juntos, eles formam um sistema poderoso, mas que deve ser usado com moderação.
defer e sua ordem
O defer empilha chamadas de função que serão executadas quando a função atual retornar. A ordem de execução é LIFO (Last In, First Out): a última chamada adiada é a primeira a executar. Isso é crucial para entender quando você tem múltiplos defers.
Um uso comum é garantir que recursos como arquivos, conexões de rede ou locks sejam liberados, mesmo se ocorrer um erro. Por exemplo, ao abrir um arquivo, você pode adiar seu fechamento imediatamente após abri-lo.
package main
import "fmt"
func main() {
defer fmt.Println("primeiro defer")
defer fmt.Println("segundo defer")
fmt.Println("executando no main")
}
Ao executar, a saída será:
executando no main
segundo defer
primeiro defer
Note que os defers são executados em ordem reversa. Isso é útil para desfazer operações na ordem correta, como fechar uma conexão antes de liberar um lock, ou desfazer uma transação em etapas.
Outro ponto importante: os argumentos de uma função adiada são avaliados no momento em que o defer é executado, não quando a função adiada é chamada. Por exemplo:
func main() {
x := 10
defer fmt.Println("valor de x:", x)
x = 20
}
Isso imprime valor de x: 10, porque o valor de x foi capturado no momento do defer.
panic
panic é uma função embutida que interrompe o fluxo normal da função atual e começa a desempilhar as chamadas, executando qualquer defer pendente ao longo do caminho. Se não for recuperado, o programa termina com uma mensagem de erro e uma stack trace.
É usado para situações em que o programa não pode continuar, como um índice fora dos limites ou uma chamada inválida. No entanto, em Go, é preferível retornar erros para a maioria dos casos, reservando panic para condições verdadeiramente excepcionais.
package main
import "fmt"
func main() {
fmt.Println("Início")
panic("algo deu errado")
fmt.Println("Fim") // nunca executa
}
Ao executar, o programa imprime Início e depois entra em pânico, exibindo uma mensagem e a stack trace.
É possível passar qualquer valor para panic, mas por convenção, usa-se uma string ou um tipo que implemente error. O runtime também gera panics para erros como acesso a memória inválida.
recover
recover é uma função que pode capturar um panic e retomar o controle. Ela só é útil quando chamada dentro de uma função adiada (defer), porque é nesse momento que o pânico está em andamento. Se chamada em qualquer outro contexto, ela retorna nil.
O padrão típico é usar defer com uma função anônima que chama recover. Se houver um pânico, recover retorna o valor passado para panic, e você pode tratar a situação. Caso contrário, retorna nil.
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recuperado do pânico:", r)
}
}()
fmt.Println("Antes do pânico")
panic("um erro grave")
fmt.Println("Depois do pânico") // não executa
}
A saída será:
Antes do pânico
Recuperado do pânico: um erro grave
Depois que recover captura o pânico, a função que contém o defer retorna normalmente, mas as funções acima na pilha não continuam; elas retornam imediatamente. Isso permite isolar o pânico em um ponto específico.
É importante notar que recover não pode capturar pânicos de goroutines diferentes; cada goroutine tem seu próprio fluxo de pânico.
Quando (não) usar
O uso de panic e recover deve ser raro. Em Go, a filosofia é tratar erros explicitamente, retornando-os. panic deve ser reservado para condições que indicam bugs ou situações impossíveis, como um índice fora dos limites (que o runtime já detecta) ou uma pré-condição violada.
recover é útil para evitar que um pânico em uma goroutine derrube todo o programa, especialmente em servidores ou aplicações de longa duração. Por exemplo, em um servidor HTTP, você pode querer capturar pânicos em handlers para retornar um erro 500 em vez de derrubar o servidor.
Evite usar panic para tratamento de erros comuns, como falha de I/O ou validação de entrada. Nesses casos, retorne um error. Usar panic para fluxo de controle é considerado má prática, pois torna o código difícil de ler e testar.
Além disso, defer é amplamente recomendado para limpeza de recursos, mas cuidado com o excesso: muitos defers em loops podem causar acúmulo de memória, pois cada chamada adiada fica pendente até o retorno da função. Nesses casos, considere usar um bloco de função separado.
Boas práticas e observações finais
Uma boa prática é usar defer para fechar recursos imediatamente após abri-los, garantindo liberação mesmo em caso de erro. Também é comum usar defer para registrar logs de saída de função, mas cuidado com a ordem.
Para panic e recover, defina uma função utilitária que encapsule o padrão de recuperação, como em:
func safeCall(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
f()
return nil
}
Isso permite que você chame funções potencialmente perigosas e converta pânicos em erros, mantendo a API limpa.
Lembre-se: defer é uma ferramenta poderosa para limpeza, mas use panic e recover com moderação. O código idiomático em Go prefere erros explícitos.
Referências
- Defer, Panic, and Recover - The Go Blog
- Documentação da builtin defer
- Documentação da builtin panic
- Documentação da builtin recover
- Spec: Handling panics
- Effective Go - Recover
Exercícios
- Exercício 1: Escreva um programa que use
deferpara imprimir três mensagens em ordem inversa à que foram adiadas. Explique a saída.✓ Resposta:
A saída é: terceiro, segundo, primeiro, pois os defers são executados em ordem LIFO.package main import "fmt" func main() { defer fmt.Println("primeiro") defer fmt.Println("segundo") defer fmt.Println("terceiro") } - Exercício 2: Crie uma função que simule uma operação que pode falhar. Use
panicpara indicar uma falha grave erecoverpara capturá-la, imprimindo uma mensagem amigável.✓ Resposta:package main import "fmt" func risky() { panic("falha crítica") } func main() { defer func() { if r := recover(); r != nil { fmt.Println("Recuperado:", r) } }() risky() } - Exercício 3: Explique por que
recoversó funciona dentro de uma função adiada. Dê um exemplo de onderecovernão funcionaria.✓ Resposta:recoversó funciona em uma função adiada porque, quando ocorre um panic, a pilha é desenrolada e as funções adiadas são executadas. Se você chamarrecoverfora de um defer, não há pânico em andamento. Exemplo:func main() { fmt.Println(recover()) // imprime nil, não captura nada panic("teste") } - Exercício 4: Escreva uma função que recebe um slice e um índice, e retorna o elemento naquele índice. Se o índice estiver fora dos limites, use
paniccom uma mensagem clara. Depois, userecoverpara capturar o pânico e retornar um erro.✓ Resposta:package main import "fmt" func getElement(s []int, i int) (elem int, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }() return s[i], nil } func main() { s := []int{1,2,3} e, err := getElement(s, 5) if err != nil { fmt.Println("Erro:", err) } else { fmt.Println("Elemento:", e) } } - Exercício 5: Discuta se é apropriado usar
panicpara tratamento de erros em uma aplicação web. Dê um exemplo de quando seria adequado usarrecover.✓ Resposta: Não é apropriado usarpanicpara erros comuns, como validação de formulário. Userecoverem um middleware de servidor HTTP para capturar pânicos inesperados e retornar um erro 500, evitando que o servidor caia. Exemplo:func middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }