11.7.1 Явные нелокальные выходы: catch и throw

Большинство конструкций управления влияют только на поток управления внутри самой конструкции. Функция throw является исключением из этого правила нормального выполнения программы: она выполняет нелокальный выход по запросу. (Существуют и другие исключения, но они предназначены только для обработки ошибок.) throw используется внутри catch и возвращается к этому catch. Например:

(defun foo-outer ()
  (catch 'foo
    (foo-inner)))

(defun foo-inner ()
  …
  (if x
      (throw 'foo t))
  …)

Форма throw, если она выполняется, немедленно передает управление обратно соответствующему catch, который немедленно возвращается. Код, следующий за throw, не выполняется. Второй аргумент throw используется в качестве возвращаемого значения catch.

Функция throw находит соответствующий catch, основываясь на первом аргументе: она ищет catch, чей первый аргумент eq с тем, что указано в throw. Если применимо более одного catch, имеет приоритет самый внутренний. Таким образом, в приведенном выше примере throw указывает foo, а catch в foo-outer указывает на ту же самую символу, поэтому catch является применимым (при условии, что между ними нет другого соответствующего catch).

Выполнение throw выходит из всех конструкций Lisp до соответствующего catch, включая вызовы функций. Когда конструкции привязки, такие как let, или вызовы функций завершаются таким образом, привязки отменяются, как и при нормальном завершении этих конструкций (см. Локальные переменные). Точно так же throw восстанавливает буфер и позицию, сохраненные save-excursion (см. Экскурсии), и статус сужения, сохраненный save-restriction. Он также выполняет любую очистку, установленную с помощью специальной формы unwind-protect, когда она выходит из этой формы (см. Очистка при нелокальных выходах).

Не обязательно, чтобы throw появлялся лексически внутри catch, к которому он переходит. Его также можно вызвать из другой функции, вызванной внутри catch. Пока throw происходит хронологически после входа в catch и хронологически до выхода из него, он имеет доступ к этому catch. Вот почему throw может использоваться в таких командах, как exit-recursive-edit, которые возвращаются в цикл команд редактора (см. Рекурсивное редактирование).

Примечание Common Lisp: В большинстве других версий Lisp, включая Common Lisp, есть несколько способов непоследовательной передачи управления: return, return-from и go, например. Emacs Lisp имеет только throw. Библиотека cl-lib предоставляет версии некоторых из них. См. Блоки и выходы в Common Lisp Extensions.

Специальная форма: catch tag body…

catch устанавливает точку возврата для функции throw. Точка возврата отличается от других таких точек возврата с помощью tag, которая может быть любым объектом Lisp, кроме nil. Аргумент tag вычисляется нормально до установления точки возврата.

При действии точки возврата catch вычисляет формы body в текстовом порядке. Если формы выполняются нормально (без ошибки или нелокального выхода) возвращается значение последней формы тела из catch.

Если во время выполнения body выполняется throw, указывающий то же самое значение tag, форма catch немедленно выходит; возвращаемое ею значение — это то, что было указано вторым аргументом throw.

Функция: throw tag value

Назначение throw — вернуться из точки возврата, ранее установленной с помощью catch. Аргумент tag используется для выбора среди различных существующих точек возврата; он должен быть eq с значением, указанным в catch. Если несколько точек возврата соответствуют tag, используется самая внутренняя.

Аргумент value используется как значение, возвращаемое из этого catch.

Если не существует точки возврата с тегом tag, то сигнализируется ошибка no-catch с данными (tag value).