Можно ли передавать функции как параметры в Go?
Да, функции можно передавать как параметры — это основа функционального стиля в Go
Вы всё сделали правильно. В Go функции — это объекты первого класса (first-class citizens), то есть их можно присваивать переменным, передавать как аргументы и возвращать из других функций. Синтаксис func(int, int) int — это не магия, а тип функции (function type). Он описывает сигнатуру: какие параметры и что возвращает.
Как работает тип функции в Go
Тип функции определяется её сигнатурой: списком типов параметров и типом возвращаемого значения. Например:
func(int, int) int— принимает два int, возвращает intfunc(string) error— принимает строку, возвращает ошибкуfunc()— не принимает ничего и ничего не возвращает
Важно: функции с разными сигнатурами — это разные типы. Вы не сможете передать функцию с сигнатурой func(int, int, int) int туда, где ожидается func(int, int) int.
Пример с разными сигнатурами — ошибка компиляции
func addThree(a, b, c int) int {
return a + b + c
}
// Ошибка: cannot use addThree (type func(int, int, int) int) as type func(int, int) int
// result := calculate(10, 5, addThree)
Передача функций с разными сигнатурами через интерфейсы
Если вам нужно передавать функции с разными сигнатурами, используйте пустой интерфейс interface{} и утверждение типа (type assertion) или, что лучше, замыкания (closures):
package main
import "fmt"
func calculate(a, b int, op func(int, int) int) int {
return op(a, b)
}
func main() {
// Замыкание — анонимная функция, "захватывающая" переменные из внешнего контекста
multiplier := 2
double := func(x, y int) int {
return (x + y) * multiplier
}
result := calculate(10, 5, double)
fmt.Println(result) // 30
// Можно передать и обычную функцию
fmt.Println(calculate(10, 5, func(a, b int) int { return a - b })) // 5
}
Продвинутый пример: функции как аргументы для обработки ошибок
package main
import (
"fmt"
"errors"
)
func safeDivide(a, b int, onError func(string)) int {
if b == 0 {
onError("деление на ноль!")
return 0
}
return a / b
}
func main() {
logError := func(msg string) {
fmt.Println("Ошибка:", msg)
}
fmt.Println(safeDivide(10, 2, logError)) // 5
fmt.Println(safeDivide(10, 0, logError)) // 0, выведет "Ошибка: деление на ноль!"
}
Подводные камни
- Совпадение сигнатур обязательно. Даже порядок параметров имеет значение:
func(int, string)иfunc(string, int)— разные типы. - Nil-функции. Если передать
nilвместо функции, программа упадёт с паникой. Всегда проверяйте на nil, если функция может быть не задана. - Производительность. Передача функций через указатели незначительно медленнее прямого вызова, но обычно это не критично.
Итог
Ваш код абсолютно корректен. Использование функций как параметров — мощный инструмент в Go. Он активно применяется в стандартной библиотеке (например, http.HandlerFunc, sort.Slice с функцией сравнения). Запоминайте главное: тип функции = её сигнатура. Если нужно больше гибкости — используйте замыкания или интерфейсы.
Альтернативный подход: использование интерфейсов вместо функций
В Go иногда удобнее определить интерфейс с одним методом, чем передавать функцию напрямую. Это особенно полезно, когда у вас есть несколько связанных операций или нужно хранить состояние.
package main
import "fmt"
// Определяем интерфейс для операции
type Operator interface {
Apply(a, b int) int
}
// Структура для сложения
type Add struct{}
func (Add) Apply(a, b int) int { return a + b }
// Структура для вычитания с состоянием
type Subtract struct {
Label string
}
func (s Subtract) Apply(a, b int) int {
fmt.Println("Операция:", s.Label)
return a - b
}
func calculate(a, b int, op Operator) int {
return op.Apply(a, b)
}
func main() {
fmt.Println(calculate(10, 5, Add{})) // 15
fmt.Println(calculate(10, 5, Subtract{"minus"})) // 5, выведет "Операция: minus"
}
Когда это лучше?
- Когда операция требует дополнительных данных (как поле
Labelв примере). - Когда нужно реализовать несколько методов для одного типа (например,
ApplyиValidate). - Когда код становится более читаемым — интерфейсы явно описывают контракт.
Оба подхода (функции как параметры и интерфейсы) имеют право на жизнь. Выбирайте тот, который делает код понятнее для вашей конкретной задачи.