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

Ответ

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

Как это работает?

Когда функция возвращает другую функцию (замыкание), эта внутренняя функция сохраняет ссылки на переменные, которые ей нужны из области видимости родительской функции. Эти переменные продолжают существовать в памяти, пока на них ссылается замыкание.

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

// adder возвращает замыкание. Каждое замыкание имеет свою собственную переменную `sum`.
func adder() func(int) int {
    sum := 0 // Эта переменная будет захвачена замыканием
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    // pos и neg - это два разных экземпляра замыкания,
    // каждый со своим собственным состоянием (переменной sum).
    pos := adder()
    neg := adder()

    fmt.Println(pos(1)) // 1
    fmt.Println(pos(2)) // 3

    fmt.Println(neg(-10)) // -10
    fmt.Println(neg(-5))  // -15
}

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

Самая распространенная проблема с замыканиями в Go связана с их использованием в циклах. Замыкание захватывает ссылку на переменную, а не ее значение в момент итерации.

Неправильно: Все горутины будут использовать последнее значение v.

for _, v := range []string{"a", "b", "c"} {
    go func() {
        // v - это одна и та же переменная на всех итерациях.
        // К моменту запуска горутины, цикл уже завершится,
        // и v будет содержать последнее значение "c".
        fmt.Println(v) 
    }()
}
// Возможный вывод: c, c, c

Правильно: Передать значение в горутину как аргумент или создать локальную копию.

for _, v := range []string{"a", "b", "c"} {
    // Решение 1: передать v как аргумент
    go func(val string) {
        fmt.Println(val)
    }(v)

    // Решение 2: создать локальную копию
    // v := v 
    // go func() {
    //     fmt.Println(v)
    // }()
}
// Вывод (в произвольном порядке): a, b, c