defer es una instrucción que pospone la ejecución de una función hasta que la función que la contiene termina su ejecución. No es solo un recurso estético para limpiar código; es una garantía de que, sin importar si la función retorna por un return normal o si ocurre un panic, el código programado se ejecutará. El mecanismo interno funciona mediante una pila (stack) LIFO (Last-In-First-Out), lo que significa que las llamadas se ejecutan en el orden inverso al que fueron declaradas. Debes usarlo siempre que gestiones recursos que requieran una liberación explícita, como descriptores de archivos o semáforos, para asegurar la limpieza incluso ante fallos inesperados. El punto más crítico que debes dominar es que los argumentos de una función pasada a defer se evalúan en el momento en que se lee la instrucción defer, no cuando la función se ejecuta realmente. Si intentas pasar una variable que va a cambiar su valor más adelante, estarás pasando el valor actual (el “viejo”), a menos que uses una función anónima para capturar la variable por referencia mediante una clausura.
package main
import (
"fmt"
)
func main() {
// Ejecutamos un proceso que simula la gestión de un recurso crítico.
ejecutarProceso()
}
func ejecutarProceso() {
status := "pendiente"
fmt.Printf("Estado inicial: %s\n", status)
// 1. El error de la evaluación de argumentos:
// El valor de 'status' se evalúa AQUÍ (es "pendiente") y se pasa como argumento.
// Aunque 'status' cambie después, este Printf usará el valor original.
defer fmt.Printf("--- [DEFER] Estado al salir (argumento evaluado antes): %s\n", status)
// 2. El uso de una clausura (closure) para capturar el valor actual:
// Al usar una función anónima, no pasamos argumentos a la clausura,
// sino que la función "atrapa" la variable 'status' por referencia.
defer func() {
fmt.Printf("--- [DEFER] Estado al salir (captura por clausura): %s\n", status)
}()
// 3. LIFO (Last-In-First-Out):
// Las funciones se ejecutarán en orden inverso a su declaración.
defer fmt.Println("--- [DEFER] Limpieza de buffers temporales...")
defer fmt.Println("--- [DEFER] Cerrando conexión a la base de datos...")
// Simulamos lógica de negocio
fmt.Println("Trabajando...")
status = "en ejecución"
fmt.Println("Finalizando tareas...")
status = "finalizado"
// Al llegar aquí, se disparan los defers en orden inverso:
// 1. Cerrando conexión...
// 2. Limpieza de buffers...
// 3. Estado al salir (clausura) -> mostrará "finalizado"
// 4. Estado al salir (argumento) -> mostrará "pendiente"
}
Desglose del comportamiento
Si analizas el código, verás que el orden de salida es crucial. Primero, se ejecutan los defer más recientes: el cierre de la conexión a la base de datos y la limpieza de buffers. Esto es ideal para arquitecturas de capas; si abres un archivo y luego una conexión, quieres que la conexión se cierre antes que el archivo.
Fíjate en la diferencia técnica entre las dos primeras llamadas diferidas. En defer fmt.Printf(..., status), el runtime de Go toma el valor de status en el momento exacto en que se lee la línea del defer. Como en ese instante status era "pendiente", ese es el valor que se guarda en la pila. En cambio, en la función anónima defer func() { ... }(), la función no recibe un valor precalculado, sino que mantiene una referencia al espacio de memoria de la variable status. Por eso, cuando la pila se vacía al final de ejecutarProceso, la clausura va a buscar el valor actual de status, que ya es "finalizado".
El error frecuente
Un error clásico que causa fugas de recursos (resource leaks) ocurre cuando usas defer dentro de un bucle for. Como los defer se ejecutan al finalizar la función que los contiene (y no al terminar la iteración del bucle), si abres muchos archivos en un ciclo, todos los descriptores permanecerán abiertos hasta que la función principal termine, lo que puede agotar los límites del sistema operativo.
// ERROR COMÚN: No cerrar archivos dentro de un loop
for _, path := range archivos {
f, err := os.Open(path)
if err != nil {
continue
}
// El archivo NO se cierra aquí. Se queda abierto hasta que la función principal termine.
defer f.Close()
// ... procesar archivo
}
// FORMA CORRECTA: Usar una función anónima para delimitar el scope
for _, path := range archivos {
err := func(p string) error {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // Se cierra al terminar esta función anónima (cada iteración)
// ... procesar archivo
return nil
}(path)
if err != nil {
// manejar error
}
}
N° 39