ProgramaciónDesarrollador Go Senior

Cuéntame cómo Go implementa cierres (func literales/closures) y cuáles son las limitaciones y características de su uso: dónde se almacenan, cómo se capturan las variables, qué diferencia hay en el comportamiento de las variables capturadas en diferentes escenarios?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

En Go, las funciones anónimas (func literales) son capaces de crear cierres, es decir, acceder a las variables de su ámbito circundante incluso después de que este ha finalizado. Tales cierres asignan memoria en el heap, si es necesario para su correcto funcionamiento (detectado a través del análisis de escape).

Ejemplo:

func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d ", a(5)) // 5 printf("%d ", a(10)) // 15

Características:

  • El cierre captura por referencia las variables del ámbito externo (y no sus valores en el momento de la creación).
  • Si la variable se modifica fuera del cierre, el cierre verá el nuevo valor.
  • Si el cierre se devuelve de una función, las variables capturadas vivirán hasta el final de la vida del closure.
  • Si el cierre no se utiliza, el análisis de escape puede permitir que las variables no se vayan al montón.

Pregunta trampa

¿Qué mostrará este código?

func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }

Muchos responderán que mostrará 0, 1, 2, sin embargo, el resultado será:

3
3
3

Todos los cierres se refieren a una misma variable i; después de que finaliza el ciclo, su valor es 3.

Correcto: capturar una copia de la variable dentro del cuerpo del ciclo:

for i := 0; i < 3; i++ { v := i // nueva variable fs = append(fs, func() { fmt.Println(v) }) }

Ejemplos de errores reales debido al desconocimiento de las sutilezas del tema


Historia

En un proyecto de enrutamiento dinámico, se utilizó un ciclo para crear múltiples handlers a través de closures, cada uno debía capturar su propia ruta. Como resultado, todos los handlers imprimían la última ruta — no se creó una variable separada en cada cierre. El error se descubrió solo durante la integración con la API HTTP.


Historia

Al probar el acceso paralelo a través de goroutines dentro de un ciclo, el closure capturaba la referencia al índice, no a la copia. Esto creaba efectos "aleatorios": los datos no se escribían en su ranura del array, sino en la última.


Historia

En una función de recopilación de estadísticas, el closure modificaba una variable global del ámbito externo, aunque el autor esperaba un contador independiente para cada tarea. El problema se notó debido a la suma inadecuadamente reconstruida, que siempre era global y no privada, a pesar de la lógica local.