- Определение и вызов анонимной функции
- Сохранение анонимной функции в переменную
- Замыкания: доступ к внешним переменным
- Анонимные функции и горутины
- Анонимные функции как аргументы других функций
- Передача анонимных функций в другие функции
- Возвращение анонимных функций (фабрики)
- Анонимные функции внутри циклов и подводные камни
- HTTP-обработчики
- Отложенные вызовы (defer)
- Выводы
В языке 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"
}
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.