miércoles, 18 de octubre de 2023

Defer en Go


La declaración de Defer de Go programa una llamada a una función para que se ejecute inmediatamente antes de que regrese el flujo de control. Es una forma inusual pero efectiva de lidiar con situaciones como recursos que deben liberarse independientemente del camino que tome una función para regresar. Los ejemplos canónicos son desbloquear un mutex o cerrar un archivo.


// Contents returns the file's contents as a string.

func Contents(filename string) (string, error) {

    f, err := os.Open(filename)

    if err != nil {

        return "", err

    }

    defer f.Close()  // f.Close will run when we're finished.


    var result []byte

    buf := make([]byte, 100)

    for {

        n, err := f.Read(buf[0:])

        result = append(result, buf[0:n]...) // append is discussed later.

        if err != nil {

            if err == io.EOF {

                break

            }

            return "", err  // f will be closed if we return here.

        }

    }

    return string(result), nil // f will be closed if we return here.

}

Diferir una llamada a una función como Close tiene dos ventajas. Primero, garantiza que nunca olvidará cerrar el archivo, un error que es fácil de cometer si luego edita la función para agregar una nueva ruta de retorno. En segundo lugar, significa que el cierre se sitúa cerca de la apertura, lo cual es mucho más claro que colocarlo al final de la función.

Los argumentos de la función diferida (que incluyen al receptor si la función es un método) se evalúan cuando se ejecuta el diferimiento, no cuando se ejecuta la llamada. Además de evitar preocupaciones acerca de que las variables cambien los valores a medida que se ejecuta la función, esto significa que un único sitio de llamada diferida puede diferir múltiples ejecuciones de funciones. He aquí un ejemplo:


for i := 0; i < 5; i++ {

    defer fmt.Printf("%d ", i)

}


Las funciones diferidas se ejecutan en orden LIFO, por lo que este código hará que se imprima 4 3 2 1 0 cuando la función regrese. Un ejemplo más plausible es una forma sencilla de rastrear la ejecución de funciones a través del programa. Podríamos escribir un par de rutinas de rastreo simples como esta:


func trace(s string)   { fmt.Println("entering:", s) }

func untrace(s string) { fmt.Println("leaving:", s) }


// Use them like this:

func a() {

    trace("a")

    defer untrace("a")

    // do something....

}

Podemos hacerlo mejor aprovechando el hecho de que los argumentos de las funciones diferidas se evalúan cuando se ejecuta el diferimiento. La rutina de rastreo puede configurar el argumento para la rutina de desrastreo. Este ejemplo:


func trace(s string) string {

    fmt.Println("entering:", s)

    return s

}


func un(s string) {

    fmt.Println("leaving:", s)

}


func a() {

    defer un(trace("a"))

    fmt.Println("in a")

}


func b() {

    defer un(trace("b"))

    fmt.Println("in b")

    a()

}


func main() {

    b()

}


Esto imprime: 


entering: b

in b

entering: a

in a

leaving: a

leaving: b


Para programadores acostumbrados a la gestión de recursos a nivel de bloques de otros lenguajes, defer puede parecer peculiar, pero sus aplicaciones más interesantes y potentes provienen precisamente de que no está basado en bloques sino en funciones.

No hay comentarios.:

Publicar un comentario