Panic e recover: erros não recuperáveis
1. Introdução ao Panic em Go
Em Go, o tratamento de erros é tradicionalmente feito através do retorno de valores do tipo error. No entanto, existe um mecanismo mais drástico para situações excepcionais: o panic. Um panic é uma interrupção abrupta do fluxo normal de execução de um programa. Quando um panic ocorre, a função atual para imediatamente, e o runtime de Go começa a desempilhar a pilha de chamadas, executando qualquer função defer que encontrar pelo caminho.
A principal diferença entre um erro comum (error) e um panic é a intencionalidade. Erros são esperados e fazem parte do fluxo normal de operação — como uma conexão de rede que falha ou um arquivo que não existe. Panics, por outro lado, representam situações que não deveriam acontecer em condições normais de operação.
Cenários típicos que disparam um panic automaticamente incluem:
- Acessar um índice fora dos limites de um slice ou array
- Tentar dereferenciar um ponteiro nulo (nil pointer dereference)
- Falha em uma asserção de tipo (type assertion)
- Chamar close() em um channel já fechado
package main
func main() {
// Este código causa um panic: índice fora dos limites
slice := []int{1, 2, 3}
_ = slice[10] // panic: runtime error: index out of range [10] with length 3
}
2. Sintaxe e comportamento do panic
Para disparar um panic manualmente, usamos a função embutida panic(). O argumento pode ser de qualquer tipo, mas por convenção, utiliza-se uma string descritiva ou um valor do tipo error.
package main
import "fmt"
func main() {
fmt.Println("Início do programa")
panic("algo muito errado aconteceu!")
fmt.Println("Esta linha nunca será executada")
}
O efeito imediato de um panic é o desempilhamento da pilha de chamadas (stack unwinding). O runtime percorre a pilha de execução de baixo para cima, executando todas as funções defer registradas. Se nenhum recover for invocado, o programa termina com uma mensagem de erro e o stack trace completo.
package main
import "fmt"
func nivel1() {
defer fmt.Println("defer em nivel1")
nivel2()
}
func nivel2() {
defer fmt.Println("defer em nivel2")
panic("panic em nivel2")
}
func main() {
defer fmt.Println("defer em main")
nivel1()
}
Saída:
defer em nivel2
defer em nivel1
defer em main
panic: panic em nivel2
goroutine 1 [running]:
main.nivel2(...)
/tmp/main.go:12
main.nivel1(...)
/tmp/main.go:7
main.main(...)
/tmp/main.go:17
3. Recover: capturando um panic
A função recover() é o mecanismo para retomar o controle após um panic. Ela só tem efeito quando chamada diretamente dentro de uma função defer. Se chamada fora de um contexto defer, ou após o desempilhamento já ter passado pelo frame da função, recover() retorna nil.
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic capturado:", r)
}
}()
panic("este panic será capturado")
fmt.Println("Isso não será executado")
}
Quando recover() é chamado com sucesso, ele interrompe o desempilhamento da pilha e retorna o valor passado para panic(). O programa então continua a execução a partir do ponto após a função defer que chamou recover.
4. Padrão defer + recover para tratamento controlado
O padrão mais comum para usar recover é encapsulá-lo dentro de uma função anônima executada via defer:
defer func() {
if r := recover(); r != nil {
log.Printf("Recuperado de panic: %v", r)
}
}()
Em alguns casos, pode ser útil relançar o panic após um tratamento parcial:
defer func() {
if r := recover(); r != nil {
log.Println("Tratamento parcial do panic")
panic(r) // relança o mesmo panic
}
}()
Um exemplo prático importante é um servidor HTTP que não deve morrer ao encontrar um panic em uma requisição:
package main
import (
"fmt"
"log"
"net/http"
)
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic capturado: %v, requisição: %s", err, r.URL.Path)
http.Error(w, "Erro interno do servidor", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func handlerQuePanica(w http.ResponseWriter, r *http.Request) {
panic("falha catastrófica!")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handlerQuePanica)
server := recoveryMiddleware(mux)
fmt.Println("Servidor rodando em :8080")
log.Fatal(http.ListenAndServe(":8080", server))
}
5. Boas práticas: quando usar panic e recover
A comunidade Go estabeleceu convenções claras sobre o uso de panic e recover:
Casos legítimos para panic:
- Bugs inesperados que nunca deveriam ocorrer em produção
- Invariantes quebradas durante o desenvolvimento
- Inicialização de pacotes que falham de forma irrecuperável
Casos inadequados:
- Usar panic como substituto para tratamento de erros normais
- Disparar panic para controle de fluxo
- Usar panic para sinalizar erros de validação de entrada
A regra de ouro: panic para erros de programação, error para erros esperados.
// Ruim: usando panic para validação de entrada
func dividir(a, b int) int {
if b == 0 {
panic("divisão por zero")
}
return a / b
}
// Bom: retornando erro
func dividir(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("divisão por zero")
}
return a / b, nil
}
6. Panic em pacotes públicos e bibliotecas
Pacotes públicos devem evitar disparar panics, pois isso quebra o controle do chamador sobre o fluxo do programa. Uma biblioteca bem comportada deve sempre retornar erros e permitir que o chamador decida como tratá-los.
Se um pacote público precisa usar panic internamente (por exemplo, para simplificar código complexo), ele deve sempre capturá-lo com recover e convertê-lo em um erro retornado:
package meupacote
import "fmt"
func OperacaoRisco() (resultado string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic interno: %v", r)
}
}()
// código que pode causar panic
realizarOperacaoArriscada()
return "sucesso", nil
}
Documente claramente na API se alguma função pode disparar panic e em quais condições.
7. Recuperando panics em goroutines filhas
Um dos aspectos mais críticos do panic em Go é que um panic em qualquer goroutine mata o programa inteiro. Isso significa que uma goroutine que não trata seus próprios panics pode derrubar todo o sistema.
package main
import (
"fmt"
"time"
)
func worker(id int) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Worker %d recuperado de panic: %v\n", id, r)
}
}()
fmt.Printf("Worker %d iniciado\n", id)
if id == 2 {
panic("falha no worker 2")
}
time.Sleep(time.Second)
fmt.Printf("Worker %d finalizado\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
fmt.Println("Programa finalizou com sucesso")
}
Cada goroutine deve ter seu próprio defer/recover para garantir que panics isolados não derrubem todo o sistema. Em pools de workers, essa prática é essencial:
func poolDeWorkers(tarefas <-chan Task) {
for i := 0; i < 5; i++ {
go func(id int) {
defer func() {
if r := recover(); r != nil {
log.Printf("Worker %d panickou: %v", id, r)
}
}()
for tarefa := range tarefas {
processarTarefa(tarefa)
}
}(i)
}
}
8. Debugando panics: stack trace e informações úteis
Quando um panic não é recuperado, Go imprime automaticamente um stack trace detalhado. Saber interpretá-lo é essencial para depuração:
panic: runtime error: index out of range [5] with length 3
goroutine 1 [running]:
main.minhaFuncao(...)
/home/user/projeto/main.go:25 +0x45
main.outraFuncao(...)
/home/user/projeto/main.go:20 +0x78
main.main(...)
/home/user/projeto/main.go:15 +0x23
Cada linha mostra:
- A função onde o erro ocorreu
- O arquivo e linha exatos
- O offset dentro da função
Para obter o stack trace sem interromper o programa, use debug.PrintStack() do pacote runtime/debug:
package main
import (
"fmt"
"runtime/debug"
)
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic:", r)
debug.PrintStack()
}
}()
panic("algo deu errado")
}
Em produção, considere usar ferramentas como pprof ou serviços de monitoramento como Sentry para capturar e analisar panics remotamente.
Referências
- Documentação oficial de panic — Artigo do blog oficial de Go explicando defer, panic e recover com exemplos práticos
- Pacote runtime/debug — Documentação oficial do pacote que fornece funções como PrintStack para depuração
- Effective Go: Panic — Seção do Effective Go sobre boas práticas com panic e recover
- Go by Example: Panic and Recover — Tutorial interativo com exemplos práticos de panic e recover
- The Go Programming Language Specification — Especificação oficial da linguagem sobre o mecanismo de handling de panics
- Dave Cheney: Don't Panic — Artigo técnico discutindo quando usar panic e quando evitar, por um dos principais autores da comunidade Go