Можно ли передать функцию как аргумент в другую функцию в Go
Что проверяют
Понимаете ли вы, что функции в Go это значения первого класса: их можно передать, вернуть, положить в переменную или поле структуры. Следом обычно просят показать замыкание или объяснить, как устроен middleware в net/http.
Как отвечать
Да, можно. Параметр объявляется типом функции, сигнатура описывается прямо в списке аргументов или через именованный тип:
- тип функции:
func(int, int) int, для читаемости его выносят вtype Op func(int, int) int. Совместимость определяется сигнатурой, имя функции роли не играет; - анонимная функция: передаётся на месте,
apply(xs, func(x int) int { return x * 2 }); - замыкание: функция захватывает переменные из внешней области и видит их изменения, на этом строятся счётчики, генераторы и функциональные опции вида
WithTimeout(d); - значение метода:
obj.Methodможно передать как обычную функцию, получатель привязывается в момент взятия значения; - дженерики: с Go 1.18 пишется универсальный
Map[T, U any](s []T, f func(T) U) []U.
Хорошо привести примеры из стандартной библиотеки: sort.Slice и slices.SortFunc принимают функцию сравнения, http.HandlerFunc превращает обычную функцию в http.Handler, а middleware имеет вид func(http.Handler) http.Handler. filepath.WalkDir вызывает вашу функцию на каждом файле.
Нулевое значение функционального типа это nil. Функции сравниваются только с nil, выражение f == g не скомпилируется.
Где спотыкаются
Передают результат вызова вместо самой функции: calculate(5, 3, add()) вместо calculate(5, 3, add). Компилятор это поймает, но на собеседовании такая ошибка выглядит плохо.
Не проверяют функцию на nil. Вызов неинициализированного колбэка даёт панику во время выполнения, поэтому необязательный параметр либо проверяют, либо подставляют функцию по умолчанию.
Путаются в замыканиях внутри цикла. До Go 1.22 переменная цикла была одна на все итерации, и горутины часто видели последнее значение. С 1.22 каждая итерация получает свою копию, и об этой разнице полезно сказать вслух.