Передаём функции в Go: от новичка до профи за 15 минут

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Можно ли передать функцию как параметр? Да, и вот почему это меняет всё

Вы когда-нибудь задумывались, можно ли в Go передать одну функцию внутрь другой, как обычную переменную? Если вы пришли из мира Python или JavaScript, для вас это привычный паттерн. Но в Go, несмотря на всю его строгость, функции как параметры — это не просто "можно", это один из самых мощных инструментов языка.

Я помню своё первое знакомство с этой концепцией: я писал утилиту для обработки логов и понял, что копипащу один и тот же цикл с разной логикой внутри. Решение пришло, когда я осознал, что могу просто передать нужную функцию как аргумент. Код стал чище, короче и понятнее. В этой статье я покажу вам, как освоить передачу функции в функцию Go — от простых примеров до реальных паттернов, которые вы сможете использовать завтра на работе.

Кстати, мы на PythonLib часто видим вопросы вроде "Can functions be passed as parameters?" на Stack Overflow с тысячами просмотров. Давайте раз и навсегда разберём эту тему на примере Go.

Что такое функции как параметры в Go?

В Go функции — это объекты первого класса. Это значит, что их можно:

  • Присваивать переменным
  • Передавать в другие функции
  • Возвращать из функций
  • Хранить в структурах и слайсах

Когда мы говорим о функции как параметре, мы имеем в виду, что одна функция принимает другую функцию в качестве аргумента и вызывает её внутри себя. Звучит сложно? На самом деле это проще, чем кажется.

Синтаксис: как объявить функцию, принимающую другую функцию

В Go нужно явно указывать тип функции-параметра. Выглядит это так:

// Объявляем тип функции
type Transformer func(string) string

// Функция, которая принимает другую функцию
func processText(text string, transform Transformer) string {
    return transform(text)
}

Или можно объявить тип прямо в сигнатуре:

func processText(text string, transform func(string) string) string {
    return transform(text)
}

Оба варианта рабочие. Первый удобнее, если вы планируете переиспользовать тип. Второй — для быстрых решений.

Простой пример: передаём функцию приветствия

Давайте напишем что-то жизненное. Представьте, что у вас есть функция, которая форматирует имя пользователя, и вы хотите подставлять разные приветствия:

package main

import "fmt"

// Тип для функции-приветствия
type Greeter func(name string) string

// Функция, принимающая приветствие
func greetUser(name string, greeter Greeter) {
    message := greeter(name)
    fmt.Println(message)
}

func main() {
    // Обычное приветствие
    normalGreet := func(name string) string {
        return "Привет, " + name + "!"
    }

    // Формальное приветствие
    formalGreet := func(name string) string {
        return "Уважаемый(ая) " + name + ", добрый день."
    }

    greetUser("Анна", normalGreet)
    greetUser("Анна", formalGreet)
}

Вывод:

Привет, Анна!
Уважаемый(ая) Анна, добрый день.

Видите? Мы просто передали разные функции в одну и ту же greetUser. Это и есть передача функции как аргумента в действии.

Callback-функции: когда нужно обработать результат

Один из самых частых сценариев использования функций как параметров — это callback'и. Вы выполняете какую-то операцию и вызываете переданную функцию, когда операция завершена.

package main

import (
    "fmt"
    "time"
)

// Функция, которая имитирует долгую загрузку данных
func fetchData(callback func(string)) {
    fmt.Println("Загрузка данных...")
    time.Sleep(2 * time.Second) // Имитация задержки
    data := "Важные данные из базы"
    callback(data) // Вызываем callback с результатом
}

func main() {
    // Передаём функцию, которая будет вызвана после загрузки
    fetchData(func(result string) {
        fmt.Println("Получено:", result)
    })
    
    fmt.Println("Программа продолжает работу...")
    time.Sleep(3 * time.Second)
}

Этот паттерн критически важен для асинхронного программирования. В Go, конечно, есть горутины и каналы, но callback'и никуда не делись — они просто работают в паре с ними.

Фильтрация данных: практический пример

Давайте реализуем утилиту для фильтрации слайса. Это классика функционального программирования в Go:

package main

import "fmt"

// Функция-фильтр принимает слайс и функцию-предикат
func filter(numbers []int, predicate func(int) bool) []int {
    var result []int
    for _, n := range numbers {
        if predicate(n) {
            result = append(result, n)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Фильтруем чётные числа
    evens := filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Чётные:", evens)

    // Фильтруем числа больше 5
    greaterThanFive := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println("Больше 5:", greaterThanFive)
}

Вывод:

Чётные: [2 4 6 8 10]
Больше 5: [6 7 8 9 10]

Обратите внимание: мы написали одну функцию filter, а логику фильтрации передаём извне. Это и есть суть передачи функции в функцию Go — отделение алгоритма от конкретной реализации.

Продвинутый паттерн: middleware в HTTP-сервере

Теперь, когда вы освоили базу, давайте посмотрим на реальный продакшн-код. В Go middleware для HTTP-сервера — это классический пример функций как параметров.

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// Тип для middleware
type Middleware func(http.HandlerFunc) http.HandlerFunc

// Логирующий middleware
func Logger(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Запрос: %s %s", r.Method, r.URL.Path)
        next(w, r) // Вызываем оригинальный обработчик
        log.Printf("Выполнено за: %v", time.Since(start))
    }
}

// Middleware для проверки авторизации
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        // Тут могла бы быть проверка токена
        next(w, r)
    }
}

// Применяем middleware к обработчику
func applyMiddleware(handler http.HandlerFunc, middlewares ...Middleware                

Блоги

Рекомендаций по книгам