El defer en Go se utiliza para posponer la ejecución de una acción hasta que la función que contiene el defer finalice. Esto es especialmente útil para asegurarse de que ciertos recursos, como archivos, conexiones o bloqueos, se liberen de manera adecuada, independientemente de cómo termine la función (ya sea normalmente o debido a un error). Las funciones diferidas se ejecutan en orden LIFO (Last In, First Out), lo que significa que el último defer declarado es el primero en ejecutarse cuando la función termina.
Este artÃculo es bastante extenso asà que lo he organizado de la siguiente manera:
Si eres nuevo en el mundo de Go te recomiendo los siguientes artÃculos anteriores:
- Go basics
- Go functions
- Go unit tests
- Go pointers
- Go interfaces
- Go structs
- Go generics
- Go errors
- Go routines
- Go strings, chars y runas
Uso básico:
Primero veamos un ejemplo en el que abrimos un fichero de forma regular sin defer donde vemos que si ocurre un error debemos mostrar un mensaje de error, cerrar el fichero y salir, mientras que si se logra leer con éxito debemos cerrar el fichero y mostar la información.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
fileInfo, err := file.Stat()
if err != nil {
fmt.Println("Error getting file info:", err)
// Close file: Error
file.Close()
return
}
// Close file: Success
file.Close()
fmt.Println("File Name:", fileInfo.Name())
fmt.Println("File Size:", fileInfo.Size(), "bytes")
fmt.Println("File Mode:", fileInfo.Mode())
}
File Name: example.txt
File Size: 0 bytes
File Mode: -rw-r--r--
En el ejemplo anterior podemos ver que hemos tenido que estar pendientes de cerrar el fichero en todos los casos, tanto cuando se accede a este de forma correcta como cuando no, tener que controlar el cierre del fichero en varias partes del código puede inducir al programador a error.
La mejor manera de evitarlo es utilizar defer justo después de haber logrado abrir el fichero, mediante defer nos aseguraremos de que el recurso se libere de manera adecuada, independientemente de cómo termine la función.
El código final quedarÃa del siguiente modo.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
// Defer the closure of the file
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println("Error getting file info:", err)
return
}
fmt.Println("File Name:", fileInfo.Name())
fmt.Println("File Size:", fileInfo.Size(), "bytes")
fmt.Println("File Mode:", fileInfo.Mode())
}
File Name: example.txt
File Size: 0 bytes
File Mode: -rw-r--r--
Ejecución de funciones anónimas:
Los defers no tienen porque limitarse a realizar operaciones sencillas como cerrar un fichero, también pueden ejecutar funciones anónimas.
package main
import (
"fmt"
)
func main() {
fmt.Println("Start of main function")
defer func() {
fmt.Println("This is a deferred function")
}()
fmt.Println("End of main function")
}
Start of main function
End of main function
This is a deferred function
Ejecución de métodos:
Otra posibilidad que ofrecen los defers es ejecutar métodos de estructuras , en este ejemplo tenemos la estructura databaseConnection con dos métodos vinculados Open()/Close(), en la función main() creamos el objeto, abrimos la conexión a la base de datos y una vez hemos conseguido abrirla, indicamos un defer db.Close() para que pase lo que pase se cierre siempre la conexión.
package main
import (
"fmt"
)
type databaseConnection struct {
connected bool
}
func (db *databaseConnection) Open() {
db.connected = true
fmt.Println("Database connection opened")
}
func (db *databaseConnection) Close() {
db.connected = false
fmt.Println("Database connection closed")
}
func main() {
db := &databaseConnection{}
db.Open()
defer db.Close()
fmt.Println("Performing database operations...")
}
Database connection opened
Performing database operations...
Database connection closed
Defer stacking:
Las funciones diferidas se ejecutan en orden LIFO (Last In, First Out), lo que significa que el último defer declarado es el primero en ejecutarse cuando la función termina. En este ejemplo podemos verlo fácilmente.
package main
import (
"fmt"
)
func main() {
fmt.Println("Start of main function")
defer fmt.Println("First deferred statement")
defer fmt.Println("Second deferred statement")
defer fmt.Println("Third deferred statement")
fmt.Println("End of main function")
}
Start of main function
End of main function
Third deferred statement
Second deferred statement
First deferred statement