Анонимные функции
Функциональный литерал это функция без имени. Это выражение, поэтому его можно поставить везде, где допустимо значение: в переменную, аргумент, возвращаемое значение, поле структуры.
Стрелочного синтаксиса или сокращённых лямбд в Go нет. Полная форма func(params) results { body } единственный способ её записать.
Захват переменных
Функциональный литерал может использовать переменные охватывающей функции. Он их не копирует: замыкание и окружающий код делят одну переменную.
Захваченная переменная живёт, пока на неё ссылается хотя бы одно замыкание, даже после того, как функция, которая её объявила, вернула управление. При необходимости компилятор переносит её в кучу; управлять этим самому не нужно.
Генераторы и счётчики
Если функция возвращает замыкание, каждая возвращённая функция получает своё приватное состояние.
Каждый вызов counter() создаёт новую n, поэтому c1 и c2 считают независимо. Никто снаружи не может прочитать или сбросить n, так что это маленькая форма инкапсуляции.
Замыкания в циклах (Go 1.22 и новее)
До Go 1.22 у цикла for была одна переменная цикла на весь цикл. Все созданные внутри замыкания захватывали эту единственную переменную и видели её последнее значение. Это был самый частый баг с замыканиями в Go.
Начиная с Go 1.22 каждая итерация получает свою копию, поэтому код ниже печатает 0 1 2:
В Go 1.21 и раньше тот же цикл, записанный как for i := 0; i < 3; i++ (range по целому числу тоже появился в 1.22), печатал 3 3 3. Новое правило действует для каждого модуля по строке go в go.mod, поэтому старый модуль сохраняет старое поведение, пока строку go не поднимут до 1.22 или выше. В старом коде внутри циклов до сих пор встречается i := i или v := v: это было ручное исправление, и теперь оно избыточно.
Правило «своя переменная на итерацию» касается только переменных, объявленных самой инструкцией for. Переменная, объявленная до цикла и обновляемая в теле, по-прежнему одна на всех:
var last string
for _, s := range items {
last = s
handlers = append(handlers, func() { use(last) }) // every closure sees the final value
}
Замыкания и горутины
Горутина, запущенная с функциональным литералом, тоже замыкание. Действуют те же правила разделения плюс обычное правило конкурентности: если несколько горутин пишут в захваченную переменную, нужен мьютекс или канал.
Программа всегда печатает 5050. Каждая горутина читает свою i (семантика циклов Go 1.22) и пишет в общий total под блокировкой. Уберите мьютекс, и результат станет непредсказуемым; go run -race сообщит о гонке данных. WaitGroup и детектор гонок описаны на странице о горутинах.
Где замыкания встречаются в реальном коде
- Сортировка и поиск:
slices.SortFunc,slices.IndexFunc,sort.Sliceпринимают функциональный литерал. - Отложенная очистка:
defer func() { ... }()выполняет блок при выходе из функции и может читать её переменные. См. defer. - HTTP middleware: функция, которая принимает обработчик и возвращает новый литерал
http.HandlerFunc, оборачивающий его. - Конфигурация: функциональные опции (
func WithTimeout(d time.Duration) Option { return func(c *Config) { c.Timeout = d } }) это замыкания над аргументом.
Пример middleware, который захватывает и параметр, и счётчик:
Частые ошибки
- Ожидание снимка значения. Замыкание читает текущее значение переменной в момент выполнения, а не то, что было при создании замыкания. Если нужен снимок, передайте значение параметром:
go func(v int) { ... }(x). - Рекурсивные литералы. Функциональный литерал не может сослаться на себя через переменную, которой он присваивается в том же
:=. Сначала объявите переменную:var walk func(n int); walk = func(n int) { ... walk(n-1) }. - Несинхронизированная запись из горутин. Захват не является синхронизацией. Защищайте общие записи.
Часто задаваемые вопросы
Что такое анонимная функция в Go?
Функциональный литерал без имени: func(x int) int { return x * 2 }. Его можно присвоить переменной, передать как аргумент, вернуть или вызвать сразу, добавив () после закрывающей скобки. Отдельного синтаксиса лямбд в Go нет; функциональный литерал и есть лямбда.
Что такое замыкание в Go?
Функциональный литерал, который обращается к переменным, объявленным снаружи. Функция сохраняет эти переменные живыми и делит их с окружающей областью видимости, поэтому изменения внутри замыкания видны снаружи, и наоборот. Переменные захватываются по ссылке, а не копируются.
Замыкания в Go захватывают переменные по значению или по ссылке?
По ссылке. Замыкание и окружающий код используют одну и ту же переменную. Если нужен снимок значения, скопируйте его в новую переменную перед созданием замыкания или передайте как аргумент.
Исправлен ли в Go баг с переменной цикла в замыканиях?
Да, начиная с Go 1.22. Каждая итерация цикла for теперь объявляет новую переменную цикла, поэтому замыкания и горутины, созданные в цикле, видят значение своей итерации. Это действует для модулей, в go.mod которых указано go 1.22 или новее. В старом коде внутри цикла часто встречается i := i как обходной приём; он безвреден, но больше не нужен.