Previous: Errors, Up: Nonlocal Exits


9.5.4 非ローカル脱出時の後始末

unwind-protect構造は、データ構造を一時的に整合性のない状態に するときには本質的です。 この構造により、エラーや非ローカル脱出が起こったときに、 データの整合性を回復できます。

— Special Form: unwind-protect body cleanup-forms...

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を設定するまえに実際に中断が行われると、 プロセスは消去されません。 このバグを直す簡単な方法はありませんが、 少なくとも、ほとんど起こりえません。