defer pospone la ejecución de una llamada a función hasta que la función que la contiene retorna. Se ejecuta aunque la función retorne normalmente, con un return anticipado o debido a un pánico. Es el mecanismo idiomático de Go para el trabajo de limpieza: cerrar ficheros, liberar mutex, cerrar conexiones de base de datos o registrar el tiempo de ejecución de una función.
Comportamiento básico
package main
import "fmt"
func main() {
fmt.Println("inicio")
defer fmt.Println("diferido")
fmt.Println("fin")
}
// inicio
// fin
// diferido
El deferred se ejecuta justo antes de que main retorne, independientemente de lo que haya ocurrido dentro de la función.
Cuándo se evalúan los argumentos
Los argumentos de la función diferida se evalúan en el momento en que se declara el defer, no cuando se ejecuta:
func main() {
x := 10
defer fmt.Println("valor de x:", x) // captura x=10 ahora
x = 99
fmt.Println("x ahora es:", x)
}
// x ahora es: 99
// valor de x: 10
Orden LIFO con múltiples defers
Cuando hay varios defers en una misma función, se ejecutan en orden inverso (último en declararse, primero en ejecutarse):
func main() {
defer fmt.Println("primero en declarar, último en ejecutar")
defer fmt.Println("segundo")
defer fmt.Println("tercero se ejecuta primero")
}
// tercero se ejecuta primero
// segundo
// primero en declarar, último en ejecutar
Cerrar ficheros con defer
El uso más habitual es garantizar que un recurso se cierre independientemente de cómo acabe la función:
func procesarFichero(ruta string) error {
f, err := os.Open(ruta)
if err != nil {
return fmt.Errorf("abrir %s: %w", ruta, err)
}
defer f.Close() // se ejecutará cuando procesarFichero retorne
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
return scanner.Err()
}
Sin defer, habría que llamar a f.Close() en cada punto de salida de la función, y es fácil olvidarlo.
defer y recover
defer es la única forma de ejecutar recover(), que intercepta un pánico y permite continuar la ejecución:
func seguro(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("pánico recuperado: %v", r)
}
}()
f()
return nil
}
func main() {
err := seguro(func() {
panic("algo salió muy mal")
})
fmt.Println(err) // pánico recuperado: algo salió muy mal
}
La trampa del defer en bucles
Poner un defer dentro de un bucle no cierra el recurso en cada iteración, sino cuando la función que contiene el bucle retorna. Si abres muchos ficheros en un bucle, podrías agotar los descriptores de fichero:
// MAL: los Close se acumulan hasta que la función retorna
for _, ruta := range rutas {
f, _ := os.Open(ruta)
defer f.Close()
procesar(f)
}
// BIEN: extrae a una función auxiliar
for _, ruta := range rutas {
procesarFichero(ruta)
}
func procesarFichero(ruta string) {
f, _ := os.Open(ruta)
defer f.Close() // se cierra al final de esta función, no del bucle
procesar(f)
}
Medir el tiempo de ejecución
func medir(nombre string) func() {
inicio := time.Now()
return func() {
fmt.Printf("%s tardó %vn", nombre, time.Since(inicio))
}
}
func tarea() {
defer medir("tarea")()
time.Sleep(100 * time.Millisecond)
}
func main() {
tarea()
// tarea tardó 100.2ms
}
