Функции как значения
Чему вы научитесь:
- передавать функцию аргументом, хранить её в переменной и возвращать из другой функции;
- объяснять, что такое замыкание и почему оно видит переменные вокруг себя;
- откладывать вызов до выхода из функции через
deferи знать порядок отложенных вызовов; - останавливать панику через
recover.

Представьте карточку с рецептом. Её можно отдать другому человеку, и он приготовит блюдо по вашим правилам, хотя сам рецепт не придумывал. Карточку можно убрать в коробку и достать позже. Сама карточка — не блюдо, а инструкция, как его сделать.
В Go функцию тоже можно передать другому коду, как такую карточку. Функция slices.Sort упорядочивает строки по алфавиту, а для сортировки по длине нужно другое правило сравнения. Правило — это код, и в Go его передают функции slices.SortFunc обычным аргументом. Представьте slices.SortFunc как сортировочную машину со щелью для карточки. Слова засыпают сверху, карточку с правилом вставляют в щель, и машина раскладывает слова так, как написано на карточке.
Функция как аргумент
slices.SortFunc принимает аргументом другую функцию:
package main
import (
"fmt"
"slices"
)
func byLen(a, b string) int { return len(a) - len(b) }
func main() {
words := []string{"kiwi", "fig", "banana"}
slices.SortFunc(words, byLen)
fmt.Println(words) // [fig kiwi banana]
}Давайте разберём, что здесь происходит:
func byLen(a, b string) intсравнивает две строки по длине и возвращает разницу длин.words := []string{"kiwi", "fig", "banana"}создаёт срез из трёх слов.slices.SortFunc(words, byLen)получаетbyLenи сама вызывает её для пар элементов.fmt.Println(words)печатает[fig kiwi banana].
Знак результата говорит slices.SortFunc, как расставить пару:
- отрицательный результат —
aдолжна стоять раньшеb; - положительный —
aдолжна стоять позжеb; - ноль — порядок неважен.
Для "fig" и "kiwi" функция byLen даёт 3 - 4, то есть -1, поэтому fig встаёт раньше.
Так одна функция slices.SortFunc сортирует по любому правилу: по длине, по последней букве, по убыванию. Правило меняется, а код сортировки остаётся прежним — меняется только карточка.
Тип функции
Имя функции без скобок — это сама функция, а не её вызов:
byLenпередаёт карточку;byLen("fig", "kiwi")готовит блюдо прямо сейчас и даёт число.
У функции как у значения есть тип. Тип функции собирают из трёх частей:
- слово
func; - типы параметров в скобках:
(string, string); - тип результата:
int.
У byLen это func(string, string) int. Такой тип можно дать переменной:
var cmp func(string, string) int
cmp = byLen
fmt.Println(cmp("fig", "kiwi")) // -1Разберём по строкам:
var cmp func(string, string) intобъявляет переменную с типом функции.cmp = byLenзаписывает в неё функциюbyLen.fmt.Println(cmp("fig", "kiwi"))вызывает функцию через переменную и печатает-1, как вызов по имени.
Функцию с другими параметрами или другим результатом компилятор в cmp не запишет.
Значения первого класса
С функцией в Go можно сделать три действия:
- присвоить переменной;
- передать аргументом;
- вернуть из другой функции.
Поэтому функции в Go называют значениями первого класса. На собеседовании часто спрашивают, что это значит, и ответ — эти три действия.
Сравнение функций
Здесь картинка с карточкой заканчивается. Две бумажные карточки можно сравнить, а две функции — нет: byLen == byLen не скомпилируется с ошибкой func can only be compared to nil.
Функцию сравнивают только с nil — нулевым значением переменной вроде cmp, пока в неё ничего не записали.
Функция без имени
Функцию можно записать прямо в выражении, не давая ей имени. Такая запись называется функциональным литералом, или анонимной функцией. Она устроена как обычное объявление функции, только между func и скобкой с параметрами нет имени.
Литерал как аргумент
Литерал пишут прямо на месте аргумента. Вызов slices.SortFunc(words, func(a, b string) int { return len(b) - len(a) }) сортирует слова от длинных к коротким.
Так удобно передавать правило, которое нужно в одном месте программы. Отдельное имя для него заводить незачем.
Литерал как результат
Литерал удобен и для возврата функции из функции:
func pick(op string) func(int, int) int {
if op == "+" {
return func(a, b int) int { return a + b }
}
return func(a, b int) int { return a * b }
}
func main() {
f := pick("*")
fmt.Println(f(3, 4), pick("+")(3, 4)) // 12 7
}Давайте разберём пример по строкам:
func pick(op string) func(int, int) intобъявляет функцию с результатом типаfunc(int, int) int.- При
op == "+"функцияpickвозвращает литерал, который складывает. - Иначе она возвращает литерал, который умножает.
f := pick("*")получает функцию умножения, поэтомуf(3, 4)даёт12.pick("+")(3, 4)сначала получает функцию сложения, а затем сразу вызывает её с3и4— получается7.
Частая ошибка
Вместо функции передают результат её вызова:
slices.SortFunc(words, byLen()) // ✗not enough arguments in call to byLenСкобки после byLen вызывают функцию прямо на этой строке, а аргументов для вызова нет. Функции SortFunc нужна сама карточка, чтобы вызывать её для каждой пары. Передавайте имя без скобок: slices.SortFunc(words, byLen).
Главное
- Функция в Go — значение: её присваивают переменной, передают аргументом и возвращают из функции.
- Тип функции записывают через
func, типы параметров и результата:func(string, string) int. - Имя без скобок передаёт саму функцию, а скобки вызывают её на месте.
- Функциональный литерал — функция без имени, записанная прямо в выражении.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.