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, и функция вернёт это значение. Используется для оборачивания ошибки, логирования или подмены возврата.

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