Что такое захват переменной в замыканиях (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

Ответ 18+ 🔞

Да ты посмотри, какая история интересная вырисовывается, блядь! Захват переменной, сука, в замыканиях — это ж как в жизни бывает: ты думаешь, что у тебя всё схвачено, а оно, блядь, уже утекло и поменялось!

Вот смотри, простой пример, чтоб мозги не ебать:

func main() {
    a := 1
    // Функция-то наша, хитрая жопа, она не значение 'a' запоминает, а саму переменную, как есть, за шкирку хватает!
    f := func() {
        fmt.Println(a) 
    }

    a = 2 // А мы тут взяли и поменяли 'a'! Уже после того, как функцию создали!
    f()   // И что она выведет, как думаешь? Правильно, 2, ёпта! Потому что она смотрит на живую переменную, а не на её старый слепок!
}

А вот тут, блядь, классика жанра, где все обламываются!

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

Так делать — себя не уважать, блядь:

for i := 0; i < 3; i++ {
    // Все три горутины, блядь, смотрят на одну и ту же 'i'! К моменту, когда они побегут, цикл уже кончится, и 'i' будет равна 3!
    go func() {
        fmt.Println(i)
    }()
}
time.Sleep(time.Second) // Ждём, пока они там все пошевелятся
// А на выходе получаем: 3, 3, 3. Во все дыры давалка, да? Охуенный результат!

А вот так — правильно, по-белому, блядь:

Надо каждой горутине свою копию значения в лапки сунуть, как аргумент передать. Тогда у каждой будет своё, родное, и драться не будут.

for i := 0; i < 3; i++ {
    // Передаём 'i' как аргумент! Теперь у каждой функции своя локальная копия 'val', и им похуй, что там с внешней 'i' происходит.
    go func(val int) {
        fmt.Println(val)
    }(i)
}
time.Sleep(time.Second)
// Вывод будет, конечно, вразнобой (горутины же!), но цифры будут правильные: 0, 1, 2. Красота, ёпта!

Вот и вся магия, блядь. Запомни: когда создаёшь замыкание в цикле, особенно с горутинами — подозрение ебать чувствуй! Сразу думай, что ты там захватываешь, а то получишь пиздопроебибну вместо ожидаемого результата.