Что такое захват переменной в замыканиях (closures)?

Ответ

Захват переменной (variable capture) — это механизм, при котором анонимная функция (замыкание) получает доступ к переменным из внешней, окружающей её области видимости. Замыкание «захватывает» не значение, а саму переменную, поэтому изменения переменной после создания замыкания будут видны внутри него.

func main() {
    a := 1
    // Замыкание захватывает переменную 'a'
    f := func() {
        fmt.Println(a) 
    }

    a = 2 // Изменяем 'a' после создания, но до вызова функции
    f()   // Выведет 2, так как функция читает актуальное значение 'a'
}

Частая ошибка: захват переменной в цикле

Это классическая проблема при использовании горутин в циклах. Все горутины захватывают одну и ту же переменную, и к моменту их выполнения цикл, скорее всего, уже завершится, и переменная будет иметь последнее значение.

Неправильно:

for i := 0; i < 3; i++ {
    // Все горутины захватывают одну и ту же переменную 'i'.
    // Скорее всего, все они выведут 3.
    go func() {
        fmt.Println(i)
    }()
}
time.Sleep(time.Second) // Ждем выполнения горутин
// Возможный вывод: 3, 3, 3

Правильно (передача значения как аргумента):

Чтобы исправить это, нужно передать значение итерационной переменной в анонимную функцию как аргумент. В этом случае для каждой горутины создается своя копия значения.

for i := 0; i < 3; i++ {
    // Передаем 'i' как аргумент. 'val' - это локальная копия для каждой горутины.
    go func(val int) {
        fmt.Println(val)
    }(i)
}
time.Sleep(time.Second)
// Вывод (в произвольном порядке): 0, 1, 2