Un pánico (panic) en Go es una señal de que algo ha ido tan mal que el programa no puede continuar de forma razonable. No es una excepción normal: detiene la goroutine actual, ejecuta todos sus defers y propaga la situación hacia arriba por la pila de llamadas hasta que el programa termina con un volcado de la traza.
Cómo funciona panic
package main
import "fmt"
func main() {
fmt.Println("inicio")
panic("algo fue irrecuperable")
fmt.Println("esto nunca se ejecuta")
}
La salida muestra el mensaje y la traza completa:
inicio
goroutine 1 [running]:
main.main()
/tmp/main.go:7 +0x68
exit status 2
Los defers sí se ejecutan antes de que el programa muera, lo que garantiza que los recursos se liberan aunque haya un pánico.
Interceptar un pánico con recover
recover() solo funciona dentro de una función diferida con defer. Si hay un pánico activo, devuelve el valor pasado a panic; si no hay pánico, devuelve nil:
func manejarPanico() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Pánico interceptado: %vn", r)
}
}()
panic("esto se recupera")
}
func main() {
manejarPanico()
fmt.Println("el programa continúa") // sí se ejecuta
}
Convertir pánico en error
El patrón habitual es envolver una función potencialmente peligrosa y devolver el pánico como un error normal:
func llamarSeguro(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case error:
err = v
default:
err = fmt.Errorf("%v", v)
}
}
}()
f()
return nil
}
func main() {
err := llamarSeguro(func() {
var s []int
_ = s[5] // índice fuera de rango: genera pánico del runtime
})
fmt.Println(err)
// runtime error: index out of range [5] with length 0
}
Cuándo usar panic
Usa panic solo en situaciones que indican un fallo de programación, nunca para gestionar errores esperados en producción:
- Estado imposible que solo puede ocurrir si hay un bug en el código.
- Configuración incorrecta al arrancar que hace inútil seguir.
- En funciones
Must*(comoregexp.MustCompile) que se usan en inicialización de variables de paquete donde el error sería un bug, no un caso de uso.
// Ejemplo de función Must típica
func MustCompilar(expr string) *regexp.Regexp {
r, err := regexp.Compile(expr)
if err != nil {
panic(fmt.Sprintf("regexp inválido %q: %v", expr, err))
}
return r
}
// En init o a nivel de paquete: un error aquí es un bug
var emailRegex = MustCompilar(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$`)
Cuándo NO usar panic
No uses panic para sustituir al tipo error en flujo normal: fichero no encontrado, respuesta 404, validación fallida o entrada incorrecta del usuario. En esos casos devuelve un error y deja que el caller decida qué hacer. Las librerías especialmente nunca deben hacer panic por condiciones que el llamador puede anticipar y gestionar.
Los pánicos del runtime
El runtime de Go genera pánicos automáticamente en situaciones como acceso a índice fuera de rango, desreferenciación de puntero nil, aserción de tipo fallida sin ok o división por cero en enteros. Todos estos pueden recuperarse con recover, aunque lo más útil es corregir el bug que los causó.
