Перейти к основному содержимому
Модуль 3 · Урок 4 из 514/50 в курсе~12 min
Уроки модуля (4/5)

Замыкания и функции как значения

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

Функциональный литерал

Функция без имени, написанная в строке. Вы позвоните ему немедленно или назначьте это:

Go
greet := func(name string) {
    fmt.Println("ciao", name)
}
greet("Anna")

// invocata immediatamente:
func() { fmt.Println("subito") }()

Тип greet — func(string).

Захват окружающей среды: закрытие

Функциональный литерал может читать и изменять переменные, видимые в его лексическая область видимости, даже после того, как внешняя функция вернулась:

Go
func counter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

c := counter()
fmt.Println(c(), c(), c())  // 1 2 3

n «живет» до тех пор, пока на него ссылается хотя бы одно замыкание. Каждый звонок в counter() создает новый, независимый n.

Закрытие как обратный вызов

Очень распространенная закономерность:

Go
nums := []int{3, 1, 2}
sort.Slice(nums, func(i, j int) bool {
    return nums[i] < nums[j]
})

Замыкание захватывает nums и определяет порядок.

Функции как параметры

Чтобы принять функцию в качестве параметра, запишите ее тип:

Go
func apply(nums []int, f func(int) int) []int {
    out := make([]int, len(nums))
    for i, n := range nums {
        out[i] = f(n)
    }
    return out
}

double := func(x int) int { return x * 2 }
apply([]int{1, 2, 3}, double)   // [2 4 6]

Если подпись становится трудно читать, определите псевдоним типа:

Go
type Transform func(int) int
func apply(nums []int, f Transform) []int { ... }

Попробуйте это

Упражнение#go.m3.l4.e1
Попыток: 0Загрузка…

Определите counter(), который возвращает замыкание: каждый вызов замыкания увеличивается и возвращает внутренний счетчик.

Загрузка редактора…
Показать подсказку

`n` должен быть объявлен ПЕРЕД возвратом; закрытие фиксирует его.

Решение доступно после 3 попыток

Упражнение#go.m3.l4.e2
Попыток: 0Загрузка…

Назначьте f литерал функции, который печатает «ciao», а затем вызовите его.

Загрузка редактора…
Показать подсказку

Синтаксис: `f := func() { ... }`, затем `f()`.

Решение доступно после 3 попыток

Quiz#go.m3.l4.e3
Готово

Что это печатает? (следите за областью закрытия)

Go
mk := func() func() int {
    x := 0
    return func() int { x++; return x }
}
a := mk()
b := mk()
fmt.Println(a(), a(), b())
Варианты ответа

Резюме

  • Функции первоклассные: назначаемые, передаваемые, возвращаемые.
  • Функциональный литерал: func(params) ret { ... }, анонимный.
  • Замыкание: захватывает переменные лексической области видимости и сохраняет их в рабочем состоянии.
  • Явный тип функции в параметрах: func(int) int. — Начиная с Go 1.22, каждая итерация for имеет собственную копию переменной цикла.