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

Анонимные функции Go: Функции

В языке Go функции можно объявлять не только с именем, но и без него. Такие функции называются анонимными. Они позволяют определять логику «на месте», без необходимости придумывать отдельное имя и выносить её в общий список функций. Это удобно в ситуациях, когда функция нужна только внутри ограниченного контекста — например, при работе с обработчиками событий, сортировкой, запуском горутин.

Представим: у нас есть программа, где надо один раз что-то посчитать. Создавать отдельную именованную функцию ради одной строчки — избыточно. В таких случаях анонимные функции помогают сделать код компактнее и читаемее.

Определение и вызов анонимной функции

Анонимная функция в Go объявляется так же, как и обычная, но без имени:

package main

import "fmt"

func main() {
    // Объявляем анонимную функцию и сразу её вызываем
    result := func(a, b int) int {
        return a + b
    }(3, 5)

    fmt.Println("Сумма:", result) // выведет: Сумма: 8
}

Здесь func(a, b int) int { return a + b } — это функция без имени. Мы сразу передали ей аргументы (3, 5) и получили результат.

Сохранение анонимной функции в переменную

Иногда удобнее сохранить функцию в переменной и использовать её несколько раз:

package main

import "fmt"

func main() {
    // Сохраняем анонимную функцию в переменную
    multiply := func(x, y int) int {
        return x * y
    }

    fmt.Println(multiply(2, 3)) // 6
    fmt.Println(multiply(4, 5)) // 20
}

Здесь multiply ведёт себя как обычная именованная функция, только её мы объявили внутри main.

Замыкания: доступ к внешним переменным

Анонимные функции могут обращаться к переменным, определённым снаружи. Это называется замыканием. Оно позволяет «захватывать» контекст, в котором функция создана.

package main

import "fmt"

func main() {
    counter := 0

    increment := func() int {
        counter++ // используем внешнюю переменную
        return counter
    }

    fmt.Println(increment()) // 1
    fmt.Println(increment()) // 2
    fmt.Println(increment()) // 3
}

Здесь increment «помнит» переменную counter, даже после первого вызова. Это делает анонимные функции удобными для создания функций-счётчиков, генераторов или обёрток.

Анонимные функции и горутины

Часто анонимные функции используют вместе с горутинами, когда нужно запустить задачу параллельно:

package main

import (
    "fmt"
    "time"
)

func main() {
    go func() {
        fmt.Println("Привет из горутины!")
    }()

    time.Sleep(time.Second) // ждём, чтобы горутина успела выполниться
}

Мы не создавали отдельную функцию, а сразу передали анонимную в go.

Анонимные функции как аргументы других функций

Go активно применяет передачу функций как параметров. Благодаря анонимным функциям это можно делать прямо «на месте»:

package main

import (
    "fmt"
    "sort"
)

func main() {
    numbers := []int{5, 2, 9, 1, 3}

    // Используем анонимную функцию для сортировки
    sort.Slice(numbers, func(i, j int) bool {
        return numbers[i] < numbers[j]
    })

    fmt.Println(numbers) // [1 2 3 5 9]
}

Мы написали правило сортировки прямо в месте вызова sort.Slice, без создания отдельной функции.

Передача анонимных функций в другие функции

Go активно использует функции как параметры. Когда мы передаём анонимную функцию прямо «на месте», код остаётся компактным и читаемым.

Пример со стандартной библиотекой strings.Map, которая применяет функцию ко всем символам строки:

package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "hello, world"

    // Анонимная функция для перевода букв в верхний регистр
    result := strings.Map(func(r rune) rune {
        if r >= 'a' && r <= 'z' {
            return r - ('a' - 'A')
        }

        return r
    }, text)

    fmt.Println(result) // HELLO, WORLD
}

Здесь мы описали логику прямо в месте вызова strings.Map. Если бы мы вынесли её в отдельную функцию, код стал бы длиннее и менее наглядным.

Возвращение анонимных функций (фабрики)

Функции могут возвращать другие функции. Если возвращаемая функция замыкает внешние переменные, мы получаем генератор поведения.

Пример: фабрика инкрементаторов.

package main

import "fmt"

// Функция возвращает анонимную функцию-счётчик

func newCounter(start int) func() int {
    count := start

    return func() int {
        count++
        return count
    }
}

func main() {
    counterA := newCounter(0)
    counterB := newCounter(100)

    fmt.Println(counterA()) // 1
    fmt.Println(counterA()) // 2
    fmt.Println(counterB()) // 101
    fmt.Println(counterB()) // 102
}

Каждый вызов newCounter создаёт своё замыкание с собственным count. В реальной жизни это часто используют для генерации идентификаторов, лимитеров и конфигурируемых обработчиков.

Анонимные функции внутри циклов и подводные камни

Очень часто разработчики запускают горутины внутри цикла. Но анонимные функции здесь могут привести к багу: они «захватывают» переменную цикла, а не её копию.

package main

import (
    "fmt"
    "time"
)

func main() {
    for i := 1; i <= 3; i++ {
        go func() {
            fmt.Println("Горутина:", i)
        }()
    }

    time.Sleep(time.Second)
}

Что выведется? Почти всегда три раза «Горутина: 4». Почему? Потому что все три анонимные функции замкнули одну и ту же переменную i, и к моменту выполнения горутин цикл уже завершился.

Как правильно:

for i := 1; i <= 3; i++ {
  go func(n int) {
    fmt.Println("Горутина:", n)
  }(i)
}

Теперь в замыкание передаётся копия i, и результат будет корректным: 1, 2, 3.

HTTP-обработчики

В веб-разработке на Go анонимные функции встречаются постоянно. В пакете net/http почти всегда используется именно этот подход:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Главная страница")
    })

    http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "О проекте")
    })

    fmt.Println("Сервер запущен на :8080")
    http.ListenAndServe(":8080", nil)
}

Мы передали анонимные функции прямо в HandleFunc, и каждая из них знает, что делать с запросом. Такой приём позволяет держать логику рядом с маршрутизацией.

Отложенные вызовы (defer)

Анонимные функции удобны, когда нужно выполнить какой-то блок кода при выходе из функции.

package main

import "fmt"

func main() {
    fmt.Println("Начало работы")

    defer func() {
        fmt.Println("Очистка ресурсов перед завершением")
    }()

    fmt.Println("Основная логика программы")
}

Здесь defer гарантирует выполнение анонимной функции в самом конце работы main. Это хороший приём для освобождения ресурсов, закрытия файлов и соединений.

Выводы

Анонимные функции в Go позволяют писать компактный код, когда функция нужна только в ограниченном контексте. Они удобны для одноразовых вычислений, передачи функций как аргументов, работы с горутинами и создания замыканий. Главная их сила — доступ к внешнему окружению и возможность держать логику «рядом с использованием».


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

Закрепим теорию урока небольшой практикой.

Напишите программу, которая объявляет анонимную функцию для нормализации пользовательского ввода: функция принимает строку и возвращает её без ведущих и замыкающих пробелов, в нижнем регистре.

Пример:

package main

func main() {
    // normalize - анонимная функция
    fmt.Println(normalize("  HeLLo  ")) // "hello"
    fmt.Println(normalize("   Go   "))  // "go"
}
Показать решение
package main

import (
    "fmt"
    "strings"
)

func main() {
    normalize := func(s string) string { return strings.ToLower(strings.TrimSpace(s)) }
    fmt.Println(normalize("  HeLLo  ")) // "hello"
    fmt.Println(normalize("   Go   "))  // "go"
}

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

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

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

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

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

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

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

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