Что выведет это замыкание counter() за три вызова и почему счётчик сохраняется?
Прочитайте фрагмент ниже. counter возвращает замыкание, которое увеличивает локальную переменную count при каждом вызове.
Определите, что выведут три вызова c(), и объясните, почему счётчик сохраняется между вызовами.
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
c := counter()
fmt.Println(c(), c(), c())
}
Определите вывод.
Выведет 1 2 3. Возвращённая функция захватывает переменную count по ссылке, а не по значению, поэтому единственный count живёт после возврата counter, и каждый вызов увеличивает ту же переменную. Второй counter() создаёт независимый count, снова начиная с 1.
- ✗Думать, что замыкание захватывает
countпо значению, сбрасывая его каждый вызов - ✗Считать, что
countосвобождается при возвратеcounter— escape-анализ переносит его в heap - ✗Ожидать, что два счётчика из
counter()делят одинcount
- →Почему escape-анализ переносит
countв heap в этом примере? - →Как изменился бы вывод, если бы
counterвозвращала два замыкания, делящих одинcount?
Что выведет код?
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
c := counter()
fmt.Println(c(), c(), c())
}
Вывод
1 2 3
Почему счётчик сохраняется
counter объявляет локальную count := 0 и возвращает замыкание, которое её захватывает по ссылке. Замыкание держит ссылку на ту же переменную, а не её копию.
Поскольку переменная переживает возврат функции (escape-анализ переносит count в heap), каждый вызов c() инкрементирует и читает тот же count: 1, затем 2, затем 3.
⚠️ Тонкость порядка: Go вычисляет аргументы Println слева направо, поэтому c(), c(), c() дают 1 2 3, а не наоборот.
Каждый новый counter() создаёт свежую переменную:
c1 := counter()
c2 := counter()
fmt.Println(c1(), c1(), c2()) // 1 2 1 — c2 независим