Go: Функции (defer, panic, recover)

23 вопросов

1 Для чего нужен defer? ▶

defer откладывает выполнение функции до выхода из текущей функции (нормального или через panic). Используется для закрытия ресурсов (файл, соединение), разблокировки мьютекса, логирования. Гарантирует выполнение при любом выходе.

Открыть отдельно →
2 В каком порядке выполняются несколько defer? ▶

В порядке LIFO (стек): последний отложенный выполняется первым. defer f1(); defer f2() - при выходе сначала f2(), затем f1(). Удобно для вложенного освобождения ресурсов в обратном порядке объявления.

Открыть отдельно →
3 Когда вычисляются аргументы у defer? ▶

Аргументы отложенной функции вычисляются в момент вызова defer, а не при выходе из функции. defer f(x) - x вычисляется сразу. Чтобы передать значение на момент выхода, используют замыкание: defer func() { f(x) }() или defer func(v int) { f(v) }(x).

Открыть отдельно →
4 Что такое panic и recover? ▶

panic(v) останавливает обычное выполнение и раскручивает стек с вызовом отложенных функций. recover() вызывается только внутри defer и возвращает аргумент panic (или nil). Восстановление после panic - только в том же горутине. После recover выполнение продолжается после defer.

Открыть отдельно →
5 Где размещать recover? Паттерн defer+recover. ▶

recover() имеет смысл только внутри отложенной функции. Паттерн: defer func() { if r := recover(); r != nil { ... } }(). Так перехватывают панику в текущей горутине. Часто в начале горутины или HTTP-обработчика, чтобы логировать и не ронять весь процесс.

Открыть отдельно →
6 Когда перехватывать panic, а когда нет? ▶

Перехватывать: на границах (горутина, HTTP-handler), когда нужно логировать и возвращать ошибку клиенту. Не перехватывать: в библиотеках (пусть вызывающий решает), при невосстановимых ошибках (out of memory и т.п.). panic часто означает ошибку программиста.

Открыть отдельно →
7 Что такое замыкание (closure) в Go? ▶

Функция, ссылающаяся на переменные из внешней области видимости. Переменные захватываются по ссылке. Пример: func f() func() int { x := 0; return func() int { x++; return x } } - возвращаемая функция увеличивает x при каждом вызове.

Открыть отдельно →
8 Что такое захват переменной в замыкании? ▶

Замыкание хранит ссылку на переменную внешней области, а не копию значения на момент создания. Изменение переменной снаружи видно в замыкании, и наоборот. В цикле все замыкания могут разделять одну переменную цикла (до Go 1.22 в for range была одна переменная на итерации).

Открыть отдельно →
9 Как вернуть несколько значений из функции? ▶

Указать несколько типов возврата: func f() (int, error) { return 1, nil }. Вызов: a, err := f() или a, _ := f(). Именованные возвращаемые значения: func f() (n int, err error) - n и err объявлены в функции, return без аргументов вернёт их.

Открыть отдельно →
10 Передача по значению и по указателю в Go? ▶

По умолчанию аргументы копируются (по значению). Изменение копии внутри функции не меняет оригинал. Для изменения передают указатель *T; копируется адрес, разыменование меняет исходную переменную. Для больших структур указатель избегает копирования.

Открыть отдельно →
11 Что делает функция init()? ▶

func init() выполняется один раз при инициализации пакета (после инициализации переменных пакета). Порядок: зависимости пакета, затем переменные пакета, затем init(). В пакете может быть несколько init(); порядок по объявлению. Не принимает аргументов и не возвращает значений.

Открыть отдельно →
12 Можно ли объявлять функции во вложенных блоках (фигурные скобки)? ▶

Да. Локальная функция может быть объявлена внутри другой функции или блока. Она замыкает переменные внешней области. Анонимные функции (литералы) тоже можно объявлять в любом блоке. Именованные вложенные функции используются реже, чем анонимные.

Открыть отдельно →
13 Что такое анонимная функция в Go? ▶

Функция без имени: func(x int) { ... }. Может присваиваться переменной, передаваться как аргумент, вызываться сразу func(){}(). Используется для defer, горутин, колбэков, замыканий. Тип - функциональный: func(int) int.

Открыть отдельно →
14 Как defer связан с именованными возвращаемыми значениями? ▶

Именованные возвраты - переменные в области видимости функции. Defer может читать и изменять их. defer func() { err = cleanup() }() - отложенная функция может записать в err, и это станет возвращаемым значением. Используется для подмены возвращаемой ошибки или логирования.

Открыть отдельно →
15 Можно ли передать функцию как аргумент другой функции? ▶

Да. Функции - first-class values. Тип параметра - сигнатура функции, например func(f func(int) int). Передают именованные функции или анонимные. Так реализуются колбэки, middleware, стратегии (sort.Slice с less), functional options.

Открыть отдельно →
16 Где можно вызывать recover? ▶

recover() возвращает не nil только внутри отложенной функции и только в той же горутине, где был panic. Вызов recover вне defer бесполезен (всегда nil). В другой горутине panic не перехватывается - каждая горутина обрабатывает свой panic.

Открыть отдельно →
17 Что такое variadic функции? ▶

Функции с переменным числом аргументов: func f(nums ...int). Внутри nums - слайс. Вызов: f(1,2,3) или f(slice...) - раскрытие слайса. Один variadic параметр в конце. Пример: fmt.Println, append.

Открыть отдельно →
18 Являются ли функции в Go first-class? ▶

Да. Функции можно присваивать переменным, передавать как аргументы, возвращать из функций, хранить в структурах и слайсах. Тип функции определяется сигнатурой. Нет методов у функций (только у типов), но функции - обычные значения.

Открыть отдельно →
19 Что такое functional options паттерн? ▶

Конфигурация через опции-функции: NewServer(WithTimeout(5), WithHost("localhost")). Each option - функция, изменяющая конфиг или сам объект. Конструктор принимает ...Option. Гибко, расширяемо, без длинного списка необязательных параметров. Популярен в Go-библиотеках.

Открыть отдельно →
20 Как передать []string в параметр ...any? ▶

Раскрыть слайс в аргументы нельзя напрямую для ...any, потому что элементы слайса не "раскладываются" в отдельные any. Нужно построить слайс []any: циклом или через slices. Либо сигнатура func f(args ...string) и вызов f(s...) для []string.

Открыть отдельно →
21 Выполнятся ли defer при os.Exit? ▶

Нет. os.Exit(code) немедленно завершает программу без выхода из функций - стек не раскручивается, defer не вызывается. Для корректного завершения лучше возвращать код из main или вызывать завершение через panic/recover на верхнем уровне (редко).

Открыть отдельно →
22 Зачем нужны именованные возвращаемые значения? ▶

Удобство: переменные объявлены в сигнатуре, return без аргументов возвращает их. Defer может читать и менять их. Документация - имена подсказывают смысл. Минус: легко забыть присвоить или переприсвоить, что ведёт к неочевидным багам. Часто используют только для error.

Открыть отдельно →
23 Может ли defer изменить возвращаемую ошибку? ▶

Да, если возвращаемое значение именованное. func f() (err error) { defer func() { err = cleanup() }(); ... } - defer может присвоить err, и функция вернёт это значение. Используется для оборачивания ошибки, логирования или подмены возврата.

Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.