Что такое замыкание (closure) в Go и в чём его особенность при работе с циклами?

Ответ

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

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

Классический пример (счётчик):

func newCounter() func() int {
    // 'sum' - это захваченная переменная
    sum := 0
    return func() int {
        sum++
        return sum
    }
}

func main() {
    counter := newCounter()
    fmt.Println(counter()) // Вывод: 1
    fmt.Println(counter()) // Вывод: 2
    // Каждый вызов работает с одной и той же переменной 'sum'
}

В этом примере анонимная функция, возвращаемая из newCounter, является замыканием. Она «помнит» свою собственную переменную sum, которая сохраняет свое состояние между вызовами.

Частая ловушка: замыкания в циклах

Очень распространённая ошибка среди новичков — неправильный захват переменной цикла в горутине.

Неправильный код:

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // Все горутины захватывают ОДНУ И ТУ ЖЕ переменную 'i'.
            // К моменту их выполнения цикл уже завершится, и 'i' будет равно 3.
            fmt.Println(i)
        }()
    }
    wg.Wait()
    // Вероятный вывод (порядок не гарантирован):
    // 3
    // 3
    // 3
}

Как это исправить?

Нужно передать значение переменной цикла как аргумент в анонимную функцию. Это создаст копию переменной для каждой итерации.

Правильный код:

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        // Передаем 'i' как аргумент, создавая локальную копию 'val'.
        go func(val int) {
            defer wg.Done()
            fmt.Println(val)
        }(i)
    }
    wg.Wait()
    // Вывод (порядок не гарантирован):
    // 0
    // 1
    // 2
}