23 вопросов
defer откладывает выполнение функции до выхода из текущей функции (нормального или через panic). Используется для закрытия ресурсов (файл, соединение), разблокировки мьютекса, логирования. Гарантирует выполнение при любом выходе.
В порядке LIFO (стек): последний отложенный выполняется первым. defer f1(); defer f2() - при выходе сначала f2(), затем f1(). Удобно для вложенного освобождения ресурсов в обратном порядке объявления.
Аргументы отложенной функции вычисляются в момент вызова defer, а не при выходе из функции. defer f(x) - x вычисляется сразу. Чтобы передать значение на момент выхода, используют замыкание: defer func() { f(x) }() или defer func(v int) { f(v) }(x).
panic(v) останавливает обычное выполнение и раскручивает стек с вызовом отложенных функций. recover() вызывается только внутри defer и возвращает аргумент panic (или nil). Восстановление после panic - только в том же горутине. После recover выполнение продолжается после defer.
recover() имеет смысл только внутри отложенной функции. Паттерн: defer func() { if r := recover(); r != nil { ... } }(). Так перехватывают панику в текущей горутине. Часто в начале горутины или HTTP-обработчика, чтобы логировать и не ронять весь процесс.
Перехватывать: на границах (горутина, HTTP-handler), когда нужно логировать и возвращать ошибку клиенту. Не перехватывать: в библиотеках (пусть вызывающий решает), при невосстановимых ошибках (out of memory и т.п.). panic часто означает ошибку программиста.
Функция, ссылающаяся на переменные из внешней области видимости. Переменные захватываются по ссылке. Пример: func f() func() int { x := 0; return func() int { x++; return x } } - возвращаемая функция увеличивает x при каждом вызове.
Замыкание хранит ссылку на переменную внешней области, а не копию значения на момент создания. Изменение переменной снаружи видно в замыкании, и наоборот. В цикле все замыкания могут разделять одну переменную цикла (до Go 1.22 в for range была одна переменная на итерации).
Указать несколько типов возврата: func f() (int, error) { return 1, nil }. Вызов: a, err := f() или a, _ := f(). Именованные возвращаемые значения: func f() (n int, err error) - n и err объявлены в функции, return без аргументов вернёт их.
По умолчанию аргументы копируются (по значению). Изменение копии внутри функции не меняет оригинал. Для изменения передают указатель *T; копируется адрес, разыменование меняет исходную переменную. Для больших структур указатель избегает копирования.
func init() выполняется один раз при инициализации пакета (после инициализации переменных пакета). Порядок: зависимости пакета, затем переменные пакета, затем init(). В пакете может быть несколько init(); порядок по объявлению. Не принимает аргументов и не возвращает значений.
Да. Локальная функция может быть объявлена внутри другой функции или блока. Она замыкает переменные внешней области. Анонимные функции (литералы) тоже можно объявлять в любом блоке. Именованные вложенные функции используются реже, чем анонимные.
Функция без имени: func(x int) { ... }. Может присваиваться переменной, передаваться как аргумент, вызываться сразу func(){}(). Используется для defer, горутин, колбэков, замыканий. Тип - функциональный: func(int) int.
Именованные возвраты - переменные в области видимости функции. Defer может читать и изменять их. defer func() { err = cleanup() }() - отложенная функция может записать в err, и это станет возвращаемым значением. Используется для подмены возвращаемой ошибки или логирования.
Да. Функции - first-class values. Тип параметра - сигнатура функции, например func(f func(int) int). Передают именованные функции или анонимные. Так реализуются колбэки, middleware, стратегии (sort.Slice с less), functional options.
recover() возвращает не nil только внутри отложенной функции и только в той же горутине, где был panic. Вызов recover вне defer бесполезен (всегда nil). В другой горутине panic не перехватывается - каждая горутина обрабатывает свой panic.
Функции с переменным числом аргументов: func f(nums ...int). Внутри nums - слайс. Вызов: f(1,2,3) или f(slice...) - раскрытие слайса. Один variadic параметр в конце. Пример: fmt.Println, append.
Да. Функции можно присваивать переменным, передавать как аргументы, возвращать из функций, хранить в структурах и слайсах. Тип функции определяется сигнатурой. Нет методов у функций (только у типов), но функции - обычные значения.
Конфигурация через опции-функции: NewServer(WithTimeout(5), WithHost("localhost")). Each option - функция, изменяющая конфиг или сам объект. Конструктор принимает ...Option. Гибко, расширяемо, без длинного списка необязательных параметров. Популярен в Go-библиотеках.
Раскрыть слайс в аргументы нельзя напрямую для ...any, потому что элементы слайса не "раскладываются" в отдельные any. Нужно построить слайс []any: циклом или через slices. Либо сигнатура func f(args ...string) и вызов f(s...) для []string.
Нет. os.Exit(code) немедленно завершает программу без выхода из функций - стек не раскручивается, defer не вызывается. Для корректного завершения лучше возвращать код из main или вызывать завершение через panic/recover на верхнем уровне (редко).
Удобство: переменные объявлены в сигнатуре, return без аргументов возвращает их. Defer может читать и менять их. Документация - имена подсказывают смысл. Минус: легко забыть присвоить или переприсвоить, что ведёт к неочевидным багам. Часто используют только для error.
Да, если возвращаемое значение именованное. func f() (err error) { defer func() { err = cleanup() }(); ... } - defer может присвоить err, и функция вернёт это значение. Используется для оборачивания ошибки, логирования или подмены возврата.