Функции
Функции в Go — множественные и именованные возвраты, вариативные параметры, функции как значения первого класса и рекурсия.
5 вопросов
JuniorТеорияОчень частоКак работают множественные и именованные возвраты в Go?
Как работают множественные и именованные возвраты в Go?
Функция в Go может вернуть сразу несколько значений, идиоматически пару (T, error) как return val, err. Результаты можно и поименовать: именованные результаты — это заранее объявленные переменные, которые стартуют с нулевого значения и остаются в области всё тело, что позволяет «голый» return, отправляющий их текущие значения. Вызывающий обязан прочитать каждое возвращённое значение или отбросить одно через _.
Типичные ошибки
- ✗Думать, что «голый»
returnигнорирует именованные результаты, а не шлёт их текущие значения - ✗Забывать, что именованный результат стартует с нулевого значения, а не «не задан» или только
nil - ✗Оставлять возвращённое значение без
_— это не компилируется
Уточняющие вопросы
- →Когда «голый»
returnвредит читаемости, хотя и легален? - →Как отложенное замыкание взаимодействует с именованным возвращаемым значением?
JuniorТеорияЧастоФункции в Go — значения первого класса. Что это означает на практике?
Функции в Go — значения первого класса. Что это означает на практике?
Первый класс значит, что функция — обычное значение: её можно присвоить переменной, передать как аргумент и вернуть из другой функции. У каждой функции есть тип-функция вроде func(int) int, описывающий параметры и результаты, поэтому компилятор проверяет эти использования по типам. Можно также написать анонимную функцию прямо в месте использования, без имени.
Типичные ошибки
- ✗Думать, что считаются лишь именованные функции верхнего уровня, а не анонимные встроенные
- ✗Забывать, что у функции-значения есть тип вроде
func(int) int - ✗Считать, что нельзя вернуть функцию из другой функции
Уточняющие вопросы
- →Как анонимная функция становится замыканием над окружающими переменными?
- →Каково нулевое значение переменной функционального типа и что будет при её вызове?
JuniorТеорияЧастоЧто такое вариативный параметр в Go и как он передаётся?
Что такое вариативный параметр в Go и как он передаётся?
Запись ...T у последнего параметра делает функцию вариативной: она собирает любое число хвостовых аргументов в срез []T, поэтому внутри тела параметр — обычный срез, по которому можно итерироваться. Вызывают её отдельными аргументами вроде f(a, b, c) или разворачивают существующий срез через f(s...). Стандартные функции вроде append и fmt.Println устроены именно так.
Типичные ошибки
- ✗Забывать, что вариативный параметр должен быть последним в сигнатуре
- ✗Передавать срез напрямую вместо разворота через форму
s... - ✗Считать собранные аргументы массивом, а не срезом
[]T
Уточняющие вопросы
- →Что даёт внутри тела передача нуля аргументов вариативному параметру?
- →Почему вариативный вызов иногда размещает базовый массив в куче?
JuniorТеорияИногдаКак работает рекурсия в Go и есть ли оптимизация хвостовых вызовов?
Как работает рекурсия в Go и есть ли оптимизация хвостовых вызовов?
Рекурсия — это функция, вызывающая саму себя, и ей нужен базовый случай, который возвращается без рекурсии, чтобы цепочка завершилась. Go НЕ гарантирует оптимизацию хвостовых вызовов, поэтому даже хвостовой рекурсивный вызов добавляет новый кадр: каждый уровень тратит ещё стека горутины, который растёт, но в итоге может переполниться. Из-за этого для горячих или очень глубоких путей предпочитают явный цикл.
Типичные ошибки
- ✗Считать, что Go делает оптимизацию хвостовых вызовов и рекурсия бесплатна по стеку
- ✗Забывать базовый случай, из-за чего рекурсия не завершается и переполняет стек
- ✗Браться за рекурсию на горячем глубоком пути, где цикл дешевле
Уточняющие вопросы
- →Какую ошибку или панику вы получите, когда рекурсия в Go исчерпает стек?
- →Чем растущий по требованию стек горутины отличается от фиксированного стека в C?
MiddleТеорияИногдаКак отложенная функция может изменить именованное возвращаемое значение?
Как отложенная функция может изменить именованное возвращаемое значение?
Именованные результаты — это настоящие переменные, остающиеся в области до фактического возврата из функции, даже после того как return их задал. Отложенное замыкание — само значение-функция первого класса — выполняется в этом окне, поэтому может прочитать и переприсвоить их: defer func(){ err = wrap(err) }() перезаписывает то, что получит вызывающий. С безымянными возвратами это невозможно, ведь не остаётся переменной, к которой отложенный вызов мог бы обратиться.
Типичные ошибки
- ✗Думать, что defer получает копию результата, а не живую переменную
- ✗Ждать того же приёма с безымянными возвратами, где переменной нет
- ✗Считать, что defer выполняется до того, как
returnзадал результат, а не после
Уточняющие вопросы
- →В каком порядке применяются несколько отложенных правок одного именованного результата?
- →Почему этот шаблон част для оборачивания или аннотирования ошибки перед выходом из функции?