defer fue concebido en Go para simplificar la gestión de recursos (por ejemplo, archivos, mutex, conexiones) — para cualquier caso en que se necesite garantizar la ejecución de operaciones al final de la ejecución de la función. Históricamente, construcciones análogas han existido en otros lenguajes (finally en Java, try-with-resources) pero Go implementa un patrón más explícito y comprensible.
Problema: Siempre se debe tener la certeza de que los recursos se liberan, incluso si ocurre un error o se produce un panic. La llamada doble a la liberación de un recurso o las fugas son problemas comunes en el estilo clásico de programación.
Solución: Todo lo que se declara mediante defer en una función o método se coloca en la pila de llamadas y se ejecutará en orden inverso antes de salir de la función. Esto garantiza la liberación de recursos incluso en caso de excepciones (panic) o retornos prematuros.
Ejemplo de código:
func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // el cierre del archivo ocurrirá al final // trabajo con el archivo return nil }
Características clave:
¿Se ejecutarán los defer si dentro de la función ocurre un panic?
¡Sí! Todas las funciones defer serán llamadas incluso en caso de panic, este es el mecanismo principal de "finalización".
¿Cuándo se evalúan los argumentos de la función pasados a defer?
En el momento de la declaración de defer, no cuando se ejecuta realmente. Por lo tanto, si se utilizan variables que luego son modificadas, esto es importante tenerlo en cuenta:
a := 1 defer fmt.Println(a) a = 2 // imprimirá 1, no 2
¿Cómo funciona defer dentro de un bucle? ¿No llevará esto a una fuga de memoria?
Si se utiliza defer en cada iteración del bucle, todos los defer se ejecutarán solo después de completar toda la función, no después de cada iteración — toda la pila de funciones defer se acumulará, lo que puede llevar a un consumo excesivo de memoria.
for i := 0; i < 3; i++ { defer fmt.Println(i) }
Se abren mil archivos en un bucle, y para cada uno se utiliza defer. Todos los archivos se cerrarán solo al final de toda la función y los recursos se retendrán, lo que llevará a una "fuga" — sobrepasando el límite de archivos abiertos.
Ventajas:
Desventajas:
En el bucle se utilizan funciones locales, donde defer se aplica solo al ámbito de este archivo, y no a todo el manejador:
for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // trabajo con f }() }
Ventajas:
Desventajas: