Funciones anónimas
Un literal de función es una función sin nombre. Es una expresión, así que puede ir en cualquier sitio donde vaya un valor: una variable, un argumento, un valor de retorno, un campo de un struct.
Go no tiene una forma abreviada con flecha ni lambda. La forma completa func(params) results { body } es la única manera de escribirla.
Capturar variables
Un literal de función puede usar variables de la función que lo contiene. No las copia: la closure y el código que la rodea comparten una única variable.
La variable capturada vive mientras alguna closure siga haciendo referencia a ella, incluso después de que haya retornado la función que la declaró. El compilador la mueve al heap cuando hace falta; tú nunca gestionas eso.
Generadores y contadores
Devolver una closure desde una función da a cada función devuelta su propio estado privado.
Cada llamada a counter() crea un n nuevo, así que c1 y c2 cuentan por separado. Nada de fuera puede leer ni reiniciar n, lo que convierte esto en una pequeña forma de encapsulación.
Closures en bucles (Go 1.22 y posteriores)
Antes de Go 1.22, un bucle for tenía una sola variable de bucle para todo el bucle. Todas las closures creadas dentro capturaban esa única variable y veían su valor final. Era el bug de closures más común en Go.
Desde Go 1.22, cada iteración tiene su propia copia, así que el siguiente código imprime 0 1 2:
Con Go 1.21 y anteriores, el mismo bucle escrito como for i := 0; i < 3; i++ (range sobre un entero también es nuevo en 1.22) imprimía 3 3 3. La nueva regla se aplica por módulo, según la línea go de go.mod, así que un módulo antiguo mantiene el comportamiento antiguo hasta que su línea go suba a 1.22 o posterior. Todavía verás i := i o v := v dentro de bucles en código antiguo: era el arreglo manual y ahora sobra.
La regla por iteración cubre solo las variables declaradas por la propia sentencia for. Una variable declarada antes del bucle y actualizada en el cuerpo sigue siendo una única variable compartida:
var last string
for _, s := range items {
last = s
handlers = append(handlers, func() { use(last) }) // every closure sees the final value
}
Closures y goroutines
Una goroutine lanzada con un literal de función también es una closure. Se aplican las mismas reglas de compartición, más la regla habitual de concurrencia: si varias goroutines escriben en una variable capturada, necesitas un mutex o un channel.
Esto siempre imprime 5050. Cada goroutine lee su propio i (semántica de bucles de Go 1.22) y escribe el total compartido bajo un lock. Quita el mutex y el resultado se vuelve impredecible; go run -race lo informa como una carrera de datos. Consulta goroutines para WaitGroup y el detector de carreras.
Dónde aparecen las closures en código real
- Ordenar y buscar:
slices.SortFunc,slices.IndexFuncysort.Slicereciben un literal de función. - Limpieza diferida:
defer func() { ... }()ejecuta un bloque al salir de la función y puede leer sus variables. Consulta defer. - Middleware HTTP: una función que recibe un handler y devuelve un nuevo literal
http.HandlerFuncque lo envuelve. - Configuración: las opciones funcionales (
func WithTimeout(d time.Duration) Option { return func(c *Config) { c.Timeout = d } }) son closures sobre el argumento.
Un ejemplo de middleware que captura tanto un parámetro como un contador:
Errores comunes
- Esperar una instantánea. Una closure lee el valor actual de la variable cuando se ejecuta, no el valor que tenía al crearse. Pasa el valor como parámetro si necesitas una instantánea:
go func(v int) { ... }(x). - Literales recursivos. Un literal de función no puede referirse a sí mismo mediante la variable a la que se asigna en el mismo
:=. Declara primero la variable:var walk func(n int); walk = func(n int) { ... walk(n-1) }. - Escrituras sin sincronizar desde goroutines. Capturar no es sincronizar. Protege las escrituras compartidas.
Preguntas frecuentes
¿Qué es una función anónima en Go?
Un literal de función sin nombre: func(x int) int { return x * 2 }. Puedes asignarla a una variable, pasarla como argumento, devolverla o llamarla de inmediato añadiendo () tras la llave de cierre. Go no tiene una sintaxis lambda aparte; el literal de función es la lambda.
¿Qué es una closure en Go?
Un literal de función que hace referencia a variables declaradas fuera de él. La función mantiene vivas esas variables y las comparte con el ámbito que la rodea, así que los cambios hechos dentro de la closure se ven fuera y viceversa. Las variables se capturan por referencia, no se copian.
¿Las closures de Go capturan variables por valor o por referencia?
Por referencia. La closure y el código que la rodea usan la misma variable. Si necesitas una instantánea, copia el valor en una variable nueva antes de crear la closure, o pásalo como argumento.
¿Está resuelto en Go el bug de la variable de bucle en closures?
Sí, desde Go 1.22. Cada iteración de un bucle for declara ahora una variable de bucle nueva, así que las closures y goroutines creadas en el bucle ven el valor de esa iteración. Se aplica a los módulos cuyo go.mod indica go 1.22 o posterior. El código antiguo suele tener i := i dentro del bucle como apaño; no hace daño, pero ya no hace falta.