Модуль
1Основы функций2Variadic-функции3Замыкания← вы здесь4defer и panic/recover
Урок 3~9 минут

Замыкания

Что такое замыкание

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

go
func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}
 
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3

Переменная count «захвачена» возвращённой функцией. Каждый вызов counter() увеличивает ту же count.


Несколько замыканий — одна переменная

Если создать два замыкания из одной фабрики — у каждого своя count:

go
c1 := makeCounter()
c2 := makeCounter()
 
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c2()) // 1 — независимый счётчик

Но если два замыкания захватывают одну и ту же переменную — они её делят:

go
x := 0
inc := func() { x++ }
get := func() int { return x }
 
inc()
inc()
fmt.Println(get()) // 2

Фабрики функций

Замыкания позволяют создавать функции, настроенные под конкретные параметры:

go
func makeMultiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}
 
double := makeMultiplier(2)
triple := makeMultiplier(3)
 
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15

Паттерн «фабрика функций» очень распространён в middleware HTTP-серверов.


Мемоизация

Замыкания удобны для кэширования результатов:

go
func memoFib() func(int) int {
    cache := map[int]int{}
    var fib func(int) int
    fib = func(n int) int {
        if n <= 1 {
            return n
        }
        if v, ok := cache[n]; ok {
            return v
        }
        result := fib(n-1) + fib(n-2)
        cache[n] = result
        return result
    }
    return fib
}
 
fib := memoFib()
fmt.Println(fib(40)) // быстро — результаты кэшируются

Ловушка в цикле

Классическая ошибка с горутинами и замыканиями:

go
// НЕПРАВИЛЬНО — все горутины захватят i = 3
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i) // вероятно: 3 3 3
    }()
}
 
// ПРАВИЛЬНО — копия i создаётся для каждой горутины
for i := 0; i < 3; i++ {
    i := i // теневая переменная
    go func() {
        fmt.Println(i) // 0 1 2 (в произвольном порядке)
    }()
}

Замыкание захватывает переменную, а не значение. К моменту выполнения горутины цикл может уже завершиться.

В Go 1.22+ поведение for-цикла изменилось: каждая итерация создаёт новую переменную, поэтому первый вариант тоже работает правильно.


Функциональные опции (паттерн)

Популярный паттерн в Go — передавать конфигурацию через функциональные опции:

go
type Server struct {
    timeout int
    maxConn int
}
 
type Option func(*Server)
 
func WithTimeout(t int) Option {
    return func(s *Server) {
        s.timeout = t
    }
}
 
func NewServer(opts ...Option) *Server {
    s := &Server{timeout: 30, maxConn: 100}
    for _, opt := range opts {
        opt(s)
    }
    return s
}
 
srv := NewServer(WithTimeout(60))

Это и есть замыкания в реальном применении — каждый With* возвращает функцию, настраивающую структуру.

Замыкание захватывает переменную, а не её значение. Если переменная изменится — замыкание увидит новое значение.
КОД
func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c1 := makeCounter()
c2 := makeCounter()

c1() // 1   ← c1 имеет свой count
c1() // 2
c2() // 1   ← c2 имеет отдельный count
c1() // 3
c1 := makeCounter()
ЗАХВАЧЕНО
count =
0
c2 := makeCounter()
ЗАХВАЧЕНО
count =
0
LOG
Нажми кнопки выше...
Каждый вызов makeCounter() создаёт новое замыкание с собственной копией переменной count. Счётчики c1 и c2 полностью независимы.
🎯
Миссия 1 из 3
Что такое замыкание?