Esta pagina se ve mejor con JavaScript habilitado

Go defer

 ·  🎃 kr0m

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:


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
Si te ha gustado el artículo puedes invitarme a un RedBull aquí