Closures y funciones anónimas
Una función anónima no tiene nombre; un closure es una función que recuerda las variables del scope donde se creó. Juntas son la base de patrones como callbacks, factories, generadores y middleware en Go.
Funciones anónimas
Una función anónima es exactamente eso: una función sin nombre. Puedes definirla y ejecutarla en el sitio, o asignarla a una variable:
package main
import "fmt"
func main() {
// Definir y ejecutar inmediatamente — IIFE
func() {
fmt.Println("Ejecutada al vuelo")
}()
// Con parámetros y argumentos
result := func(a, b int) int {
return a + b
}(3, 4)
fmt.Println(result) // 7
// Asignar a una variable y reusar
square := func(n int) int {
return n * n
}
fmt.Println(square(5)) // 25
fmt.Println(square(9)) // 81
}
Ejecutada al vuelo
7
25
81¿Qué es un closure?
Un closure es una función anónima (o cualquier función literal) que captura variables del scope que la rodea. Esas variables siguen vivas mientras el closure pueda usarlas:
package main
import "fmt"
// Devuelve un closure que "recuerda" prefix
func makeGreeter(prefix string) func(string) string {
return func(name string) string {
return prefix + ", " + name + "!"
}
}
func main() {
hello := makeGreeter("Hola")
goodMorning := makeGreeter("Buenos días")
fmt.Println(hello("Ada")) // Hola, Ada!
fmt.Println(hello("Carlos")) // Hola, Carlos!
fmt.Println(goodMorning("Ada")) // Buenos días, Ada!
}
Hola, Ada!
Hola, Carlos!
Buenos días, Ada!Go captura las variables del enclosing scope por referencia, no por valor. Si modificas la variable original, el closure verá el nuevo valor. Esto tiene implicaciones importantes (ver más abajo).
El contador clásico
El ejemplo canónico: un generador de IDs o contador con estado privado. La variable count es accesible solo desde el closure — un patrón de encapsulación sin necesidad de structs:
package main
import "fmt"
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := counter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3
// Cada llamada a counter() crea un estado independiente
other := counter()
fmt.Println(other()) // 1 — empieza desde cero
fmt.Println(next()) // 4 — el primer contador sigue avanzando
}
1
2
3
1
4La trampa del loop variable
Antes de Go 1.22, las variables declaradas en el for se compartían entre todas las iteraciones. Capturarlas en un closure llevaba a un bug clásico:
package main
import "fmt"
func main() {
funcs := []func() int{}
// En Go 1.22+ cada iteración crea su propia 'i'
// En Go <1.22 todas comparten la misma 'i' y al final vale 3
for i := 0; i < 3; i++ {
funcs = append(funcs, func() int {
return i
})
}
for _, f := range funcs {
fmt.Println(f())
}
// Go 1.22+: 0 1 2
// Go <1.22: 3 3 3
}
0
1
2Si trabajas con una versión anterior, la solución idiomática era crear una copia local en cada iteración:
for i := 0; i < 3; i++ {
i := i // shadow: nueva variable por iteración
funcs = append(funcs, func() int { return i })
}
Desde Go 1.22 esto ya no es necesario — el compilador lo hace por ti.
Casos de uso reales
Los closures aparecen por todas partes en Go idiomático: callbacks, middlewares, defer, sync.Once, opciones funcionales y más.
package main
import (
"fmt"
"sort"
)
func main() {
// sort.Slice usa un closure como comparador
people := []struct {
Name string
Age int
}{
{"Ada", 36}, {"Bob", 29}, {"Carla", 41},
}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
for _, p := range people {
fmt.Println(p.Name, p.Age)
}
// Closure capturando un acumulador
sum := 0
apply := func(n int) {
sum += n
}
for _, n := range []int{1, 2, 3, 4, 5} {
apply(n)
}
fmt.Println("Total:", sum) // Total: 15
}
Bob 29
Ada 36
Carla 41
Total: 15