Cierre en Go: comprensión y uso de clausuras

Errores comunes con asignaciones fuera de funciones

En Go, solo se permiten declaraciones a nivel de paquete fuera del cuerpo de una función. Las asignaciones deben realizarse dentro de un bloque funcional. Por ejemplo, el siguiente código es inválido:

var i int
i = 1 // Error: sentencia no declarativa fuera del cuerpo de función

func main() {
    println(i)
}

El error ocurre porque i = 1 es una asignación, no una declaración, y no puede estar fuera de cualquier función. La forma correcta es mover la asignación al interior de main:

var i int

func main() {
    i = 1
    println(i) // Salida: 1
}

Ámbito y visibilidad de variables

  • Variables globales: declaradas a nivel de paquete, accesibles desde cualquier parte del mismo.
  • Variables locales: definidas dentro de bloques (funciones, bucles, condicionales), su vida termina al salir del bloque.
  • Sombreado de variables: si una variable local tiene el mismo nombre que una global, la local oculta a la global dentro de su ámbito.
  • Visibilidad entre paquetes: los identificadores que comienzan con mayúscula son exportados (accesibles desde otros paquetes); los que comienzan con minúscula son privados.

Funciones anónimas en Go

Go permite crear funciones sin nombre, conocidas como funciones anónimas, que pueden asignarse a variables o ejecutarse inmediatamente:

f := func(nombre string) {
    fmt.Printf("Hola, %s\n", nombre)
}
f("Carlos")

// Ejecución inmediata
func() {
    fmt.Println("Ejecución inmediata")
}()

Ejemplo práctico con defer y captura de estado

Este ejemplo muestra cómo intercatúan defer y las funciones anónimas respecto al valor de las variables:

func demoDefer() {
    i := 0
    defer func() {
        fmt.Printf("defer con clausura: i=%d\n", i) // Captura el valor final de i
    }()
    defer fmt.Printf("defer inmediato: i=%d\n", i) // Se evalúa ahora, imprime 0
    for i = 0; i < 10; i++ {
    }
    fmt.Printf("valor final: i=%d\n", i) // Imprime 10
}

Salida esperada:

valor final: i=10
defer inmediato: i=0
defer con clausura: i=10

La función diferida anónima accede a i por referencia, por lo que ve su valor cuando realmente se ejecuta (después del bucle).

Paso de funciones como parámetros

Las funciones son ciudadanos de primera clase en Go. Pueden pasarse como argumentos:

func operar(a, b int, op func(int, int) int) int {
    return op(a, b)
}

func suma(a, b int) int { return a + b }
func resta(a, b int) int { return a - b }

func ejemploOperar() {
    x := operar(5, 3, suma)  // 8
    y := operar(5, 3, resta) // 2
    fmt.Printf("x=%d, y=%d\n", x, y)
}

Comprensión de cierres (clausuras)

Un cierre combina una función con el entorno de variables que rodea su definición. Este entorno permite que la función "recuerde" variables externas incluso después de que salgan del ámbito normal.

func contador(inicio int) func() int {
    valor := inicio
    return func() int {
        valor++
        return valor
    }
}

func usarContador() {
    next := contador(10)
    fmt.Println(next()) // 11
    fmt.Println(next()) // 12
    fmt.Println(next()) // 13
}

La variable valor persiste mientras exista la función retornada. Cada llamada modifica el mismo valor.

Múltiples funciones compartiendo estado

Varias funciones pueden compartir el mismo contexto de clausura:

func fabricarSumaResta(inicio int) (func(int) int, func(int) int) {
    acumulado := inicio
    sumar := func(n int) int {
        acumulado += n
        return acumulado
    }
    restar := func(n int) int {
        acumulado -= n
        return acumulado
    }
    return sumar, restar
}

func demoCompartido() {
    sumar, restar := fabricarSumaResta(10)
    fmt.Println(sumar(5))  // 15
    fmt.Println(restar(3)) // 12
    fmt.Println(sumar(1))  // 13
}

Pitfall común con goroutines y bucles

Un error frecuente ocurre cuando se usan goroutines dentro de un bucle sin capturar correctamente el valor del iterador:

func malEjemplo() {
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i) // ¡Todos imprimirán 5!
        }()
    }
    time.Sleep(time.Second)
}

El problema: todas las goroutines comparten la misma variable i, que alcanza el valor 5 antes de que cualquiera de ellas se ejecute.

Solución: pasar el valor como parámetro para romper la dependencia directa:

func buenEjemplo() {
    for i := 0; i < 5; i++ {
        go func(valor int) {
            fmt.Println(valor) // Imprime valores distintos
        }(i)
    }
    time.Sleep(time.Second)
}

Al pasar i como argumento, cada goroutine recibe una copia independiente del valor actual del iterador.

Etiquetas: cierre Go goroutine función-anónima ámbito-de-variable

Publicado el 8-11 15:51