defer, panic & recover
defer programa la limpieza al salir de una función, panic inicia el desenrollado de la pila y recover puede detener ese desenrollado, pero solo dentro de una llamada diferida.
Busca en todas las páginas de la documentación
defer programa la limpieza al salir de una función, panic inicia el desenrollado de la pila y recover puede detener ese desenrollado, pero solo dentro de una llamada diferida.
Estos tres mecanismos son la respuesta de Go a la limpieza estructurada sin excepciones para errores normales.
defer introduce llamadas en una pila LIFO por goroutine que se ejecuta cuando la función circundante retorna.
Los argumentos de las funciones diferidas se evalúan inmediatamente, pero la llamada espera hasta el retorno.
panic ejecuta las llamadas diferidas mientras busca un recover.
recover retorna el valor del panic solo durante ese desenrollado.
Los fallos esperados deben usar retornos error, no panic.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
package main
import (
"fmt"
"os"
)
func readConfig(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
// Programa el cierre del archivo para que se ejecute cuando la función retorne
defer f.Close()
buf := make([]byte, 64)
n, err := f.Read(buf)
if err != nil {
return "", err
}
return string(buf[:n]), nil
}
func main() {
text, err := readConfig("config.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(text)
}Cuándo usar esto:
defer inmediatamente después de la adquisición exitosa de un recurso (Open, Lock, BeginTx).panic para estados imposibles que indiquen errores de programación, no fallos de I/O.recover en los límites del proceso (middleware HTTP, nivel superior de un worker) para registrar y retornar 500.error para cualquier cosa que un llamador pueda manejar o reintentar razonablemente.package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
// Asegura que wg.Done() se llame al final, incluso si ocurre un panic
defer wg.Done()
// Programa una función para recuperar cualquier panic
defer func() {
if r := recover(); r != nil {
fmt.Printf("worker %d recuperado: %v\n", id, r)
}
}()
// Simula un error en el worker 2
if id == 2 {
panic("error simulado")
}
fmt.Println("worker", id, "ok")
}
func main() {
var wg sync.WaitGroup
for id := 1; id <= 3; id++ {
wg.Add(1)
go worker(id, &wg)
}
wg.Wait()
fmt.Println("todos los workers terminaron")
}Lo que esto demuestra:
defer wg.Done() se ejecuta incluso si panic ocurre más tarde en la función.defer interno con recover captura el panic sin terminar todo el programa.main.defer.defer, no en el momento del desenrollado (cuidado con las variables de bucle y los punteros).panic, el runtime ejecuta las funciones diferidas de adentro hacia afuera hasta que recover detiene la propagación o la pila termina.recover() fuera de una función diferida siempre retorna nil.| Fase | Qué sucede |
|---|---|
defer f() | Inserta f en la pila de defer; evalúa los argumentos ahora |
| Retorno normal | Ejecuta todos los defers LIFO, luego retorna al llamador |
panic(v) | Comienza el desenrollado; ejecuta los defers LIFO en esta goroutine |
recover() en defer | Detiene el desenrollado; retorna v a la función diferida |
| Panic no recuperado | Causa un crash en la goroutine / proceso con rastreo de pila |
// Un resultado con nombre + defer puede ajustar los valores de retorno
func divide(a, b int) (q int, err error) {
defer func() {
if b == 0 {
err = fmt.Errorf("división por cero")
}
}()
// Cuidado: la división aún causa panic por cero en esta forma ingenua
return a / b, nil
}
// Prefiere una comprobación explícita antes de la división en lugar de depender del panicfor { defer f() } hace crecer la pila de defer hasta que la función retorna. Solución: envuelve el cuerpo del bucle en una función anidada o usa defer fuera del bucle principal.defer; las clausuras en literales de función diferida ven los valores finales. Solución: pasa los valores como parámetros: defer func(v int) { ... }(i).recover() retorna nil y no detiene el panic. Solución: llama a recover solo dentro de defer func() { ... }().recover vacío oculta errores. Solución: registra la pila, incrementa métricas, retorna 500 a los clientes.error o un resultado (T, bool).defer a la función externa o usa limpieza explícita cuando se demuestre que es un cuello de botella.| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
Retorno error | Fallos esperados | Roturas de invariantes verdaderamente irrecuperables |
sync.Once para limpieza | Limpieza única | Recursos por solicitud |
Cancelación de context.Context | Propagar el apagado | Simples Close() en un solo manejador |
Patrón try/finally vía defer | Siempre se necesita limpieza | El lenguaje carece de finally; defer es el idioma |
| Supervisor de procesos | Aislar workers que fallan | Manejadores HTTP en proceso (usar recover) |
En la declaración defer, no cuando se ejecuta la llamada diferida.
Las variables de bucle en los argumentos requieren una copia cuidadosa.
El último diferido se ejecuta primero (LIFO).
Refleja el orden de adquisición de recursos en orden inverso para la liberación.
Sí, ese es un caso de uso principal para la limpieza.
Las funciones diferidas se ejecutan mientras la pila se desenrolla.
Recover detiene el desenrollado en la goroutine actual.
La ejecución continúa después de la función diferida que recuperó, no en el sitio del panic.
Las bibliotecas deben retornar errores para fallos operativos.
Solo usar panic para mal uso que los llamadores no puedan prevenir.
Un nuevo panic durante el desenrollado puede anular el valor del panic anterior.
Mantén las funciones diferidas simples y sin panic.
Un pequeño costo fijo por defer.
Evita miles de defers en una sola función; refactoriza los bucles.
Sí, defer f.Close() y defer mu.Unlock() son estándar.
El receptor se evalúa con otros argumentos en el momento del defer.
No, cada goroutine se desenrolla de forma independiente.
El middleware recover solo protege la goroutine del manejador que envuelve.
Usa debug.Stack() o runtime.Stack dentro del bloque defer recover.
Incluye IDs de solicitud del contexto para servicios HTTP.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (GC por defecto Green Tea, go fix modernizers - verificar parche en build), chi (última - verificar en build), gin (última - verificar en build), echo (última - verificar en build), google.golang.org/grpc (última - verificar en build), sigs.k8s.io/controller-runtime (última - verificar en build), kubebuilder (última - verificar en build), tinygo (última - verificar objetivos de placa en build), wazero (última - verificar en build), y golangci-lint (última - verificar conjunto de linters en build).
Revisado por Chris St. John·Última actualización: 18 jul 2026