UNWIND-PROTECT 특수 연산자
UNWIND-PROTECT 특수 연산자 (unwind-protect)
unwind-protect는 보호 대상 폼(protected-form)이 어떻게 끝나든, 종료 전에 반드시 정리 폼(cleanup-form)을 실행시키는 특수 연산자예요. 예외가 발생하거나 throw·return-from 같은 비지역 제어 이동으로 함수가 튀어나가도 정리 코드가 반드시 돌아간다는 보장을 줘요. 그래서 리소스 정리나 상태 복구 같은 부수 효과를 안전하게 보장할 때 쓰입니다.
문법 (Syntax)
unwind-protect protected-form cleanup-form*
=> result*
인자와 값 (Arguments and Values)
protected-form— 폼이에요.cleanup-form— 폼이에요.results—protected-form의 값들이에요.
설명 (Description)
unwind-protect는 protected-form을 평가하고, unwind-protect가 정상으로 끝나든 어떤 제어 이동으로 중단되든, 종료되기 전에 cleanup-form들이 실행됨을 보장해요. protected-form의 평가 이후에 특정 부수 효과가 반드시 일어나게 하려는 목적으로 써요.
cleanup-form 실행 중에 비지역 종료(non-local exit)가 발생하면 특별한 조치는 취해지지 않아요. unwind-protect의 cleanup-form들은 그 unwind-protect가 보호하지 않아요.
unwind-protect는 protected-form에서 빠져나가려는 모든 시도 — go, handler-case, ignore-errors, restart-case, return-from, throw, with-simple-restart — 로부터 보호해요.
종료 중에 handler·restart 바인딩의 해제는, 다이내믹 변수·catch 태그의 바인딩 해제와 평행하게, 만들어졌던 순서의 역순으로 일어나요. 그 결과 cleanup-form은 unwind-protect에 들어갈 때 보이던 것과 같은 handler·restart 바인딩, 다이내믹 변수 바인딩, catch 태그를 보게 돼요.
예제 (Examples)
(tagbody
(let ((x 3))
(unwind-protect
(if (numberp x) (go out))
(print x)))
out
...)
go가 실행되면 print 호출이 먼저 일어나고, 그 다음에 out 태그로의 제어 이동이 완료돼요. 정리 폼이 종료 전에 실행됨을 보여주는 예제예요.
(defun dummy-function (x)
(setq state 'running)
(unless (numberp x) (throw 'abort 'not-a-number))
(setq state (1+ x))) => DUMMY-FUNCTION
(catch 'abort (dummy-function 1)) => 2
state => 2
(catch 'abort (dummy-function 'trash)) => NOT-A-NUMBER
state => RUNNING
(catch 'abort (unwind-protect (dummy-function 'trash)
(setq state 'aborted))) => NOT-A-NUMBER
state => ABORTED
마지막 줄이 핵심이에요. dummy-function 'trash가 throw로 NOT-A-NUMBER를 던지며 끝나도, unwind-protect 덕분에 state가 'aborted로 정리된 다음 제어가 넘어가요.
아래 코드는 올바르지 않아요:
(unwind-protect
(progn (incf *access-count*)
(perform-access))
(decf *access-count*))
incf가 끝나기 전에 종료가 일어나면 decf 폼은 어쨌든 실행돼서 *access-count* 값이 틀어져요. 올바른 방법은 원래 값을 먼저 저장해 두는 거예요.
(let ((old-count *access-count*))
(unwind-protect
(progn (incf *access-count*)
(perform-access))
(setq *access-count* old-count)))
다음 예제들도 참고할 만해요.
;;; The following returns 2.
(block nil
(unwind-protect (return 1)
(return 2)))
;;; The following has undefined consequences.
(block a
(block b
(unwind-protect (return-from a 1)
(return-from b 2))))
;;; The following returns 2.
(catch nil
(unwind-protect (throw nil 1)
(throw nil 2)))
영향 (Affected By)
없음.
예외 상황 (Exceptional Situations)
없음.
함께 보기 (See Also)
catch,go,handler-case,restart-case,return,return-from,throw, Section 3.1 (Evaluation)
참고 (Notes)
없음.