unwind-protect
構文は、データ構造を一時的に不整合な状態にする場合に不可欠です。これにより、エラーや throw
が発生した際に、データを再び整合性のある状態に戻すことができます。(バッファ内容の変更にのみ使用される、より特化した別のクリーンアップ構文として、アトミック変更グループがあります。アトミック変更グループ。)
unwind-protect は、いかなる経緯で制御が body-form を離れたとしても cleanup-forms
が評価されるという保証のもとで body-form を実行する。body-form は正常に終了する場合もあれば、unwind-protect から throw
を実行する場合やエラーを発生させる場合もあるが、いずれの場合においても cleanup-forms
は評価される。
body-formが正常に終了した場合、unwind-protectはcleanup-formsを評価した後にbody-formの値を返します。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 "Logged in")
(error "Ftp login failed")))
(or win (and process (delete-process process)))))
この例には小さなバグがあります。ユーザーが終了するために C-g と入力し、関数 ftp-setup-buffer が戻った直後、かつ変数 process
が設定される前に終了処理が行われた場合、プロセスは強制終了されません。このバグを簡単に修正する方法はありませんが、少なくとも発生する可能性は極めて低いです。