Что такое замыкания (closures) в Go и как они работают с переменными из внешней области видимости?

Ответ

Да, это классический пример замыкания (closure).

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

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

Функция-замыкание не просто копирует значение переменной, а получает на неё ссылку. Это позволяет не только читать, но и изменять её состояние.

Пример 1: Простой доступ

package main

import "fmt"

func main() {
    message := "Это секрет"

    // Эта анонимная функция является замыканием.
    // Она "захватывает" переменную message из окружения main.
    printSecret := func() {
        fmt.Println(message)
    }

    printSecret() // Выведет: Это секрет
}

Пример 2: Изменение состояния (классический пример счётчика)

Этот пример наглядно показывает, что замыкание работает с состоянием, а не с копией.

package main

import "fmt"

// newCounter возвращает функцию-замыкание.
// Каждый вызов newCounter создает свой собственный экземпляр `count`.
func newCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    counterA := newCounter()
    fmt.Println(counterA()) // Выведет: 1
    fmt.Println(counterA()) // Выведет: 2

    // Создадим другой, независимый счётчик
    counterB := newCounter()
    fmt.Println(counterB()) // Выведет: 1
}

Важное уточнение:
Не стоит путать замыкания с видимостью на уровне пакета (неэкспортируемые переменные, начинающиеся с маленькой буквы). Замыкание — это механизм, связанный с лексической областью видимости функций, а экспорт/импорт — с видимостью между пакетами.