Что такое замыкание (closure) в Go и как оно работает?

Ответ

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

Ключевые особенности:

  1. Захват переменных: Функция захватывает переменные из внешней области, к которым она обращается.
  2. Время жизни: Захваченные переменные продолжают существовать до тех пор, пока на них ссылается замыкание, даже если внешняя функция уже завершила свою работу.
  3. Изменение переменных: Замыкание может изменять значение захваченных переменных.

Простой пример:

func greeter() func(string) {
    prefix := "Hello, "
    // Эта анонимная функция является замыканием
    return func(name string) {
        fmt.Println(prefix + name) // Использует переменную 'prefix' из внешней функции
    }
}

func main() {
    g := greeter()
    g("Alice") // Выведет: Hello, Alice
    g("Bob")   // Выведет: Hello, Bob
}

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

// Неправильно: все горутины выведут "10", т.к. i к моменту их запуска уже будет равно 10
for i := 0; i < 10; i++ {
    go func() {
        fmt.Println(i)
    }()
}

// Правильно: передаем значение i в замыкание как аргумент
for i := 0; i < 10; i++ {
    go func(val int) {
        fmt.Println(val)
    }(i)
}

Ответ 18+ 🔞

А, замыкания, ёпта! Это ж как тот чувак, который ушёл из дома, но всё равно помнит, где у него в холодильнике колбаса лежит, блядь. Функция, которая тащит с собой кусок того места, где её родили, даже если её уже далеко унесло.

Вот смотри, сука, по-простому: это анонимная функция, которая хапает переменные из той области, где её объявили, и таскает их с собой нахуй, куда бы её ни послали. Она их помнит, она к ним доступ имеет, и, что самое пиздатое, может их даже менять!

Что важно, блядь:

  1. Хапнула и не отпускает: Как мартышлюшка с бананом — раз схватила переменную извне, так и тащит её в своей жопе.
  2. Живут пока жива функция: Эти захваченные переменные не сдыхают, даже когда внешняя функция уже давно кончилась. Они будут жить, пока живо само замыкание. Магия, ёпта!
  3. Может и накосячить: Не только читать, но и писать в них может. Осторожнее там, а то начудишь.

Пример, чтобы не быть мудаком:

func greeter() func(string) {
    prefix := "Hello, " // Вот эту переменную...
    // ...эта анонимная пиздюлина сейчас захватит и утащит с собой!
    return func(name string) {
        fmt.Println(prefix + name) // Смотри-ка, а `prefix`-то тут как тут, блядь!
    }
}

func main() {
    g := greeter() // Функция `greeter` отработала, `prefix` вроде бы должен сдохнуть...
    g("Alice") // Ан нет, сука! Выведет: Hello, Alice
    g("Bob")   // И тут выведет: Hello, Bob. Переменная-то жива!
}

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

// Классический пиздец: все горутины выведут "10", потому что они все смотрят на ОДНУ И ТУ ЖЕ переменную i, которая к моменту их запуска уже стала десяткой!
for i := 0; i < 10; i++ {
    go func() {
        fmt.Println(i) // Все смотрят на одну жопу, простите, на одну переменную i
    }()
}

// Правильно, как умный человек: передаём копию значения на каждом шаге.
for i := 0; i < 10; i++ {
    go func(val int) { // Вот тут val — это уже ЛОКАЛЬНЫЙ аргумент, своя копия для каждой горутины
        fmt.Println(val)
    }(i) // И передаём текущее i сюда, блядь!
}

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