11.7.4 Очистка после неконтекстных выходов

Конструкция unwind-protect необходима всякий раз, когда вы временно приводите структуру данных в несогласованное состояние; она позволяет вам восстановить согласованность данных в случае ошибки или вызова throw. (Другая, более специфическая конструкция очистки, используемая только для изменений содержимого буферов — это атомарная группа изменений; Атомарные группы изменений.)

Специальная форма: unwind-protect body-form cleanup-forms…

unwind-protect выполняет body-form с гарантией того, что cleanup-forms будут вычислены, если управление покинет body-form, независимо от того, как это произойдёт. body-form может завершиться нормально, выполнить throw из unwind-protect или вызвать ошибку; во всех случаях cleanup-forms будут вычислены.

Если body-form завершается нормально, unwind-protect возвращает значение body-form после вычисления cleanup-forms. Если body-form не завершается, unwind-protect не возвращает никакого значения в обычном смысле.

Только body-form защищён конструкцией unwind-protect. Если какая-либо из cleanup-forms сама совершит неконтекстный выход (через throw или ошибку), то unwind-protect не гарантирует вычисление остальных. Если сбой одной из cleanup-forms может привести к проблемам, защитите её ещё одним unwind-protect вокруг этой формы.

Например, здесь мы создаём невидимый буфер для временного использования и гарантируем его уничтожение перед завершением:

(let ((buffer (get-buffer-create " *temp*")))
  (with-current-buffer buffer
    (unwind-protect
        body-form
      (kill-buffer buffer))))

Можно подумать, что мы могли бы просто написать (kill-buffer (current-buffer)) и обойтись без переменной buffer. Однако показанный выше способ безопаснее, если body-form случайно вызовет ошибку после переключения на другой буфер! (Как вариант, можно обернуть body-form в save-current-buffer, чтобы временный буфер снова стал текущим к моменту его уничтожения.)

Emacs включает стандартный макрос with-temp-buffer, который раскрывается примерно в тот же код, что показан выше (см. Текущий буфер). Некоторые макросы, описанные в этом руководстве, используют unwind-protect аналогичным образом.

Вот реальный пример, взятый из пакета FTP. Он создаёт процесс (см. Процессы) для попытки установить соединение с удалённой машиной. Поскольку функция ftp-login весьма подвержена множеству проблем, которые автор функции не может предусмотреть, она защищена формой, гарантирующей удаление процесса в случае сбоя. В противном случае Emacs может заполниться бесполезными дочерними процессами.

(let ((win nil))
  (unwind-protect
      (progn
        (setq process (ftp-setup-buffer host file))
        (if (setq win (ftp-login process host user password))
            (message "Выполнен вход")
          (error "Ошибка входа по FTP")))
    (or win (and process (delete-process process)))))

В этом примере есть небольшая ошибка: если пользователь нажмёт C-g для выхода, и выход произойдёт сразу после возврата из функции ftp-setup-buffer, но до того, как переменная process будет установлена, процесс не будет уничтожен. Исправить эту ошибку простым способом нельзя, но, по крайней мере, она маловероятна.