Можно ли передавать функции как параметры в Go?
Да, функции можно передавать как параметры — это одна из ключевых фич Go
В Go функции являются объектами первого класса. Это означает, что их можно присваивать переменным, передавать как аргументы и возвращать из других функций. Ваш пример абсолютно корректен и является идиоматическим способом работы с функциями высшего порядка в Go.
Давайте разберём детали и покажу несколько полезных паттернов.
Объявление типов функций
Вместо того чтобы писать полную сигнатуру функции в параметрах каждый раз, вы можете объявить тип функции с помощью type. Это делает код чище и переиспользуемым:
package main
import "fmt"
// Объявляем тип функции
type Transformer func(int) int
func applyToSlice(s []int, fn Transformer) []int {
result := make([]int, len(s))
for i, v := range s {
result[i] = fn(v)
}
return result
}
func double(x int) int {
return x * 2
}
func main() {
nums := []int{1, 2, 3, 4}
// Передаём именованную функцию
doubled := applyToSlice(nums, double)
fmt.Println(doubled) // [2 4 6 8]
// Передаём анонимную функцию (замыкание)
tripled := applyToSlice(nums, func(x int) int {
return x * 3
})
fmt.Println(tripled) // [3 6 9 12]
}
Функции с несколькими параметрами и возвращаемыми значениями
Вы можете передавать функции любой сложности. Главное — чтобы сигнатура совпадала:
type Comparator func(int, int) int
func sortWithComparator(s []int, cmp Comparator) {
// Простая сортировка пузырьком с кастомным компаратором
n := len(s)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if cmp(s[j], s[j+1]) > 0 {
s[j], s[j+1] = s[j+1], s[j]
}
}
}
}
func main() {
nums := []int{3, 1, 4, 1, 5, 9}
// Сортировка по возрастанию
sortWithComparator(nums, func(a, b int) int {
return a - b
})
fmt.Println(nums) // [1 1 3 4 5 9]
// Сортировка по убыванию
sortWithComparator(nums, func(a, b int) int {
return b - a
})
fmt.Println(nums) // [9 5 4 3 1 1]
}
Подводные камни и best practices
- Замыкания (closures) — анонимные функции могут захватывать переменные из внешней области видимости. Будьте осторожны с переменными цикла — в старых версиях Go (до 1.22) была известная проблема с захватом одной и той же переменной.
- Производительность — вызов функции через параметр имеет небольшой overhead, но в 99% случаев это несущественно. Для критичных участков можно рассмотреть inline-реализацию.
- Типобезопасность — Go проверяет сигнатуру функции на этапе компиляции. Вы не сможете передать
func(int) intтуда, где ожидаетсяfunc(string) string. - Методы как параметры — методы тоже можно передавать, но через синтаксис
obj.Method. При этом метод преобразуется в функцию с получателем в качестве первого аргумента.
Продвинутый пример: middleware chain
В реальных проектах часто используют цепочки функций-обработчиков:
type Handler func(string) string
func chain(h ...Handler) Handler {
return func(input string) string {
result := input
for _, handler := range h {
result = handler(result)
}
return result
}
}
func main() {
trimSpaces := func(s string) string {
return strings.TrimSpace(s)
}
toUpper := func(s string) string {
return strings.ToUpper(s)
}
process := chain(trimSpaces, toUpper)
fmt.Println(process(" hello world ")) // "HELLO WORLD"
}
Ваш подход абсолютно правильный. Используйте именованные типы для сложных сигнатур, не бойтесь замыканий, и помните о типовой безопасности — компилятор вас не подведёт.
<<Альтернативный подход: интерфейсы вместо функций
В Go есть и другой способ абстрагировать поведение — через интерфейсы. Иногда это бывает удобнее, особенно когда функция должна иметь состояние или несколько методов:
type Transformer interface {
Transform(x int) int
}
type Doubler struct{}
func (d Doubler) Transform(x int) int {
return x * 2
}
type Adder struct {
Value int
}
func (a Adder) Transform(x int) int {
return x + a.Value
}
func applyToSlice(s []int, t Transformer) []int {
result := make([]int, len(s))
for i, v := range s {
result[i] = t.Transform(v)
}
return result
}
func main() {
nums := []int{1, 2, 3}
// Используем структуру без состояния
fmt.Println(applyToSlice(nums, Doubler{})) // [2 4 6]
// Используем структуру с состоянием
fmt.Println(applyToSlice(nums, Adder{Value: 10})) // [11 12 13]
}
Какой подход выбрать? Если вам нужна просто функция без состояния — используйте передачу функции как параметра. Это проще и лаконичнее. Если нужно хранить состояние, конфигурацию или реализовывать несколько методов — интерфейсы будут более уместны. В стандартной библиотеке Go можно встретить оба подхода: sort.Slice использует функцию-компаратор, а sort.Sort — интерфейс с методами Len, Less, Swap.