unwind-protect
構造は、データ構造を一時的に整合性のない状態に
するときには本質的です。
この構造により、エラーや非ローカル脱出が起こったときに、
データの整合性を回復できます。
unwind-protect
は、bodyからどのように制御が離れた場合にも cleanup-formsの実行を保証して、bodyを実行する。 bodyは通常どおり完了するか、throw
を実行してunwind-protect
から脱出するか、 エラーを引き起こす。 いずれの場合でも、cleanup-formsは評価される。フォームbodyが正常に終了すると、
unwind-protect
は、cleanup-formsを評価したあとに、 フォームbodyの最後の値を返す。 フォームbodyが完了しなかった場合、unwind-protect
は普通の意味での値は返さない。
unwind-protect
が保護するのはbodyだけである。 cleanup-formsそのもののどれかが(throw
やエラーで) 非ローカル脱出を行うと、unwind-protect
は、 cleanup-formsの残りを評価することを保証しない。 cleanup-formsのどれかが失敗するとトラブルになる危険性がある場合には、 cleanup-formsを別のunwind-protect
で保護する。フォーム
unwind-protect
の現在の入れ子の個数は、 ローカル変数束縛の個数とともに数えられ、max-specpdl-size
に制限されている(see Local Variables)。
たとえば、表示しないバッファを一時的に作成し、 終了前に確実にそれを消去したいとしましょう。
(save-excursion (let ((buffer (get-buffer-create " *temp*"))) (set-buffer buffer) (unwind-protect body (kill-buffer buffer))))
変数buffer
を使わずに(kill-buffer (current-buffer))
と
書くだけで十分だと考えるかもしれません。
しかし、別のバッファに切り替えたあとでbodyでエラーが発生した場合には、
上の方法はより安全です。
(あるいは、bodyの周りに別のsave-excursion
を書いて、
一時バッファを消去するときに、それがカレントバッファになることを
保証する。)
Emacsには、上のようなコードに展開されるwith-temp-buffer
という
標準マクロがあります(see Current Buffer)。
本書で定義しているマクロのいくつかでは、
このようにunwind-protect
を使っています。
ファイルftp.elから持ってきた実際の例を示しましょう。
リモートの計算機への接続を確立するプロセス(see Processes)を作ります。
関数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)))))
この例には、小さなバグが1つあります。
ユーザーがC-gを打って中断しようとして、かつ、
関数ftp-setup-buffer
の終了後に
変数process
を設定するまえに実際に中断が行われると、
プロセスは消去されません。
このバグを直す簡単な方法はありませんが、
少なくとも、ほとんど起こりえません。