Зарегистрируйтесь, чтобы продолжить обучение

Функции как возвращаемое значение Go: Функции

В Go функция — это значение. Точно так же, как переменной можно присвоить число, строку или структуру, переменной можно присвоить и функцию. Раз функция — значение, значит, её можно вернуть из другой функции.

Что значит «возвращать функцию»? Это значит, что в return мы можем не число, строку или структуру вернуть, а саму функцию. На первый взгляд звучит странно, но на деле это очень удобно: мы один раз описываем, как именно должна работать новая функция, и потом получаем её «на руки» в виде результата.

Сделаем функцию, которая просто возвращает другую функцию, печатающую сообщение.

package main

import "fmt"

// makePrinter возвращает функцию, которая печатает "Hello!"
func makePrinter() func() {
    return func() {
        fmt.Println("Hello!")
    }
}

func main() {
    printer := makePrinter() // получили функцию
    printer()                // вызвали её
    printer()                // можем вызывать сколько угодно
}

Здесь makePrinter вернула функцию без имени (func() { ... }). Эта функция умеет печатать строку. Мы сохранили её в переменной printer и теперь можем вызывать как обычную функцию.

Возврат функции с параметрами

Можно сделать и посложнее: возвращаемая функция может принимать аргументы. Например, пусть мы заранее «сконфигурируем» приветствие, а потом просто будем передавать имя.

package main

import "fmt"

// greeter возвращает функцию, которая приветствует с заданным приветствием
func greeter(greeting string) func(string) {
    return func(name string) {
        fmt.Printf("%s, %s!\n", greeting, name)
    }
}

func main() {
    hello := greeter("Привет")    // функция с фиксированным "Привет"
    bonjour := greeter("Bonjour") // функция с фиксированным "Bonjour"

    hello("Иван")    // Привет, Иван!
    bonjour("Marie") // Bonjour, Marie!
}

Смысл такой: функция greeter «запоминает» слово приветствия и возвращает функцию, которая его использует. В итоге мы получаем разные функции — hello и bonjour — каждая со своим заранее выбранным поведением.

Возврат функции с внутренним состоянием

Иногда хочется не только вернуть функцию, но и чтобы она хранила у себя какое-то состояние. Это очень полезно. Например, сделаем счётчик.

package main

import "fmt"

// makeCounter возвращает функцию-счётчик
func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    counterA := makeCounter()
    counterB := makeCounter()

    fmt.Println(counterA()) // 1
    fmt.Println(counterA()) // 2
    fmt.Println(counterA()) // 3
    fmt.Println(counterB()) // 1 — у второго свой счётчик
}

Здесь переменная count не пропадает после выхода из makeCounter, потому что возвращаемая функция продолжает её использовать. Это называется замыкание. У каждого вызова makeCounter будет своё собственное хранилище count.

Ещё пример: фабрика умножителей

Допустим, нам часто нужно умножать числа на разные коэффициенты. Вместо того чтобы всё время писать x * 2, x * 3, x * 10, мы можем создать «фабрику функций»:

package main

import "fmt"

// multiplier возвращает функцию, которая умножает число на factor
func multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

func main() {
    double := multiplier(2) // функция "умножить на 2"
    triple := multiplier(3) // функция "умножить на 3"

    fmt.Println(double(5)) // 10
    fmt.Println(triple(5)) // 15
}

Мы заранее фиксируем множитель, а дальше получаем готовые функции «умножитель на 2», «умножитель на 3» и так далее.

Где это реально применяется

  1. Настраиваемые обработчики. В веб-фреймворках часто нужно вернуть функцию-обработчик HTTP-запроса, в которую уже «зашиты» какие-то данные или настройки.
  2. Фабрики и конструкторы. Когда нужно вернуть готовый «инструмент» с определённым поведением.
  3. Инкапсуляция состояния. Счётчики, кэши, трекеры — всё это удобно делать через возвращаемые функции.
  4. Декораторы. Можно возвращать новую функцию, которая оборачивает старую, добавляя проверку, логирование или обработку ошибок.

Функции как возвращаемое значение — это способ создавать новые функции «на лету». Мы можем один раз описать, как они должны работать, и потом получать разные варианты с нужными настройками. Это даёт гибкость: можно хранить внутри функции состояние, можно заранее фиксировать параметры, можно возвращать функции-обработчики прямо из фабрик.

Вместо того чтобы писать кучу повторяющегося кода, мы создаём функцию, которая делает функции. Это и есть главное удобство.


Самостоятельная работа

Потренируемся создавать функции, которые возвращают другие функции и сохраняют внутреннее состояние между вызовами.

Реализуйте функцию makeAverager, которая возвращает функцию для вычисления среднего значения всех переданных чисел.

Каждый вызов возвращаемой функции добавляет новое число в набор и возвращает текущее среднее.

Пример использования:

avg := makeAverager()

fmt.Println(avg(10)) // 10.0
fmt.Println(avg(20)) // 15.0
fmt.Println(avg(30)) // 20.0

Подсказки:

  • Используйте замыкание: внутри сохраняйте сумму и количество элементов.
  • Не храните все числа в срезе — достаточно аккумулировать сумму и счётчик.
Показать решение
package main

import "fmt"

func makeAverager() func(float64) float64 {
    var sum float64
    var count int
    return func(x float64) float64 {
        count++
        sum += x
        return sum / float64(count)
    }
}

func main() {
    avg := makeAverager()

    fmt.Println(avg(10)) // 10.0
    fmt.Println(avg(20)) // 15.0
    fmt.Println(avg(30)) // 20.0
}

Дополнительные материалы

  1. Go Spec — Function literals

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff