O que é Panic em Go?
A função built-in panic em Go interrompe imediatamente o fluxo normal de execução de uma goroutine. Quando um panic é disparado, a função atual para de executar, todas as funções defer pendentes são executadas em ordem LIFO, e então o programa termina com uma mensagem de erro e um stack trace completo.
Panic é o mecanismo de Go para lidar com situações verdadeiramente excepcionais — erros que indicam bugs no programa, estados impossíveis ou falhas irrecuperáveis. Diferente de linguagens como Java ou Python que usam exceções para controle de fluxo, Go reserva panic para cenários onde continuar a execução seria perigoso ou sem sentido.
Sintaxe básica
package main
import "fmt"
func main() {
fmt.Println("Início do programa")
panic("algo terrível aconteceu!")
fmt.Println("Esta linha nunca executa")
}
Saída:
Início do programa
goroutine 1 [running]:
main.main()
/tmp/main.go:6 +0x...
exit status 2
O argumento passado para panic() pode ser qualquer valor — uma string, um error, ou qualquer outro type. Na prática, strings e errors são os mais comuns.
Quando Go Entra em Pânico Automaticamente
O runtime de Go dispara panics automáticos em várias situações. Entender esses cenários é fundamental para escrever código robusto e evitar crashes inesperados em produção.
Acesso a índice fora dos limites
Acessar um array ou slice com um índice inválido causa panic:
func exemploIndiceFora() {
numeros := []int{1, 2, 3}
fmt.Println(numeros[10]) // panic: runtime error: index out of range [10] with length 3
}
Dereferência de ponteiro nil
Tentar acessar um campo ou método de um pointer nil causa panic:
type Usuario struct {
Nome string
}
func exemploPonteiroNil() {
var u *Usuario // ponteiro nil
fmt.Println(u.Nome) // panic: runtime error: invalid memory address or nil pointer dereference
}
Type assertion inválida
Uma type assertion que falha sem a segunda variável de retorno causa panic:
func exemploTypeAssertion() {
var i interface{} = "texto"
n := i.(int) // panic: interface conversion: interface {} is string, not int
fmt.Println(n)
}
A forma segura usa a vírgula-ok pattern:
func typeAssertionSegura() {
var i interface{} = "texto"
n, ok := i.(int)
if !ok {
fmt.Println("Conversão falhou, n tem valor zero:", n)
}
}
Envio em channel fechado
Enviar dados para um channel que já foi fechado causa panic:
func exemploChannelFechado() {
ch := make(chan int)
close(ch)
ch <- 42 // panic: send on closed channel
}
Acesso concorrente a map
Ler e escrever em um map simultaneamente de múltiplas goroutines sem mutex causa panic:
func exemploMapConcorrente() {
m := map[string]int{}
go func() {
for i := 0; i < 1000; i++ {
m["chave"] = i
}
}()
go func() {
for i := 0; i < 1000; i++ {
_ = m["chave"]
}
}()
// panic: concurrent map read and map write
}
Panic vs Error: Quando Usar Cada Um
Em Go, a filosofia é clara: use error para situações esperadas e panic para situações inesperadas. Essa distinção é fundamental para escrever código Go idiomático.
// CORRETO: erro esperado — retorne error
func abrirArquivo(caminho string) (*os.File, error) {
f, err := os.Open(caminho)
if err != nil {
return nil, fmt.Errorf("falha ao abrir %s: %w", caminho, err)
}
return f, nil
}
// CORRETO: situação impossível — use panic
func deveSerPositivo(n int) {
if n <= 0 {
panic(fmt.Sprintf("valor deve ser positivo, recebeu: %d", n))
}
}
Situações onde panic é aceitável:
| Situação | Exemplo |
|---|---|
| Bug no programa | Índice fora dos limites que não deveria acontecer |
| Inicialização falhou | Configuração obrigatória ausente no startup |
| Invariante violada | Estado interno inconsistente |
| Erro de programação | Chamada de func com argumento claramente inválido |
Situações onde você deve usar error:
| Situação | Exemplo |
|---|---|
| Arquivo não encontrado | os.Open() retorna error |
| Conexão recusada | Falha de rede é esperada |
| Input inválido do usuário | Validação de dados de entrada |
| Timeout | Operações com context expirado |
Defer Executa Durante Panic
Uma característica crucial: funções registradas com defer são executadas mesmo durante um panic. Isso garante que recursos sejam liberados corretamente:
func processarComRecurso() {
fmt.Println("Abrindo recurso")
defer fmt.Println("Fechando recurso") // executa mesmo com panic
fmt.Println("Processando...")
panic("erro crítico!")
fmt.Println("Nunca chega aqui")
}
Saída:
Abrindo recurso
Processando...
Fechando recurso
goroutine 1 [running]:
main.processarComRecurso()
...
Essa é a base do padrão panic/defer/recover, que permite interceptar panics e transformá-los em errors controlados.
Stack Trace e Diagnóstico
O stack trace gerado por um panic contém informações valiosas para diagnóstico. Ele mostra toda a cadeia de chamadas de função que levou ao panic:
package main
func a() { b() }
func b() { c() }
func c() { panic("erro na função c") }
func main() {
a()
}
O stack trace mostra cada função na pilha de chamadas, com arquivo e número da linha, facilitando a localização do bug. Em produção, ferramentas como o pacote runtime/debug permitem capturar e logar stack traces programaticamente:
import "runtime/debug"
func logPanic() {
defer func() {
if r := recover(); r != nil {
stackTrace := debug.Stack()
log.Printf("Panic recuperado: %v\nStack: %s", r, stackTrace)
}
}()
// código que pode entrar em pânico
}
Panic em Goroutines
Um panic em uma goroutine que não é recuperado derruba todo o programa, não apenas aquela goroutine. Isso é intencional em Go — um panic não tratado indica um bug sério:
func main() {
go func() {
panic("panic na goroutine!") // derruba todo o programa
}()
time.Sleep(time.Second)
fmt.Println("Nunca chega aqui")
}
Por isso, é boa prática adicionar recover em goroutines que podem entrar em pânico, especialmente em servidores HTTP e workers de background:
func workerSeguro(id int) {
defer func() {
if r := recover(); r != nil {
log.Printf("Worker %d: panic recuperado: %v", id, r)
}
}()
// trabalho que pode causar panic
processarTarefa(id)
}
func main() {
for i := 0; i < 10; i++ {
go workerSeguro(i)
}
select {} // bloqueia para sempre
}
Padrões Avançados com Panic
Panic para interromper recursão profunda
Em algoritmos recursivos, panic pode ser usado como mecanismo de “saída rápida” combinado com recover:
func buscarEmArvore(no *No, alvo int) (resultado *No, encontrado bool) {
defer func() {
if r := recover(); r != nil {
if no, ok := r.(*No); ok {
resultado = no
encontrado = true
}
}
}()
buscarRecursivo(no, alvo)
return nil, false
}
func buscarRecursivo(no *No, alvo int) {
if no == nil {
return
}
if no.Valor == alvo {
panic(no) // "salta" direto para o recover
}
buscarRecursivo(no.Esquerda, alvo)
buscarRecursivo(no.Direita, alvo)
}
Must pattern para inicialização
O padrão “Must” é comum na biblioteca padrão e em código de inicialização:
// regexp.MustCompile entra em panic se a regex for inválida
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
// template.Must entra em panic se o template for inválido
var tmpl = template.Must(template.ParseFiles("layout.html"))
// Criando seu próprio Must
func MustConnect(dsn string) *sql.DB {
db, err := sql.Open("postgres", dsn)
if err != nil {
panic(fmt.Sprintf("falha ao conectar ao banco: %v", err))
}
return db
}
Esse padrão é aceitável porque é usado durante a inicialização do programa — se a configuração está errada, é melhor falhar rápido do que continuar em estado inconsistente.
Boas Práticas
- Prefira errors a panics: Use o sistema de error de Go para a maioria dos casos
- Panic apenas para bugs: Situações que indicam erro de programação
- Proteja goroutines: Sempre adicione recover em goroutines de longa duração
- Use Must na inicialização: Aceitável para configuração no startup
- Nunca use panic para controle de fluxo: Isso vai contra a filosofia de Go
- Documente panics: Se uma func pode entrar em panic, documente isso
- Teste panics: Use
assert.Panics()ou verifique com recover nos testing
Próximos Passos
Para aprofundar seus conhecimentos sobre tratamento de erros e recuperação em Go, explore os seguintes recursos:
- Recover — como interceptar e tratar panics
- Defer — mecanismo de limpeza que funciona com panic
- Error — o sistema principal de tratamento de erros em Go
- Goroutine — entenda como panics afetam goroutines
- Tratamento de erros em Go — guia completo sobre errors
- Testes em Go — como testar cenários de panic
- Padrões de concorrência — recover em goroutines
Perguntas Frequentes
O que acontece quando um panic não é recuperado?
Quando um panic não é interceptado por recover, o programa imprime o valor do panic e um stack trace completo, e então termina com código de saída 2. Se o panic ocorre em uma goroutine, todo o programa é encerrado, não apenas aquela goroutine.
Qual a diferença entre panic e os.Exit?
panic() executa todas as funções defer pendentes antes de encerrar e gera um stack trace. os.Exit() encerra o programa imediatamente sem executar defers e sem stack trace. Use panic para erros que precisam de diagnóstico; use os.Exit() para saídas controladas.
Posso passar qualquer tipo de valor para panic?
Sim, panic() aceita qualquer valor como argumento — string, error, struct, ou qualquer outro type. Porém, na prática, use strings descritivas ou valores error para facilitar o diagnóstico quando o panic for capturado por recover.
Quando devo usar panic em vez de retornar error?
Use panic apenas para situações verdadeiramente irrecuperáveis: bugs no programa, invariantes violadas, ou falhas de inicialização (padrão Must). Para qualquer situação esperada — como arquivo não encontrado, input inválido, ou timeout — use o sistema de error padrão de Go. A regra geral: se o chamador pode razoavelmente lidar com a situação, retorne error.