When this matters
Use ordinary if, cond, and procedures first. Reach for call/cc only when a deeply nested computation needs one explicit upward escape, and add dynamic-wind when entry and cleanup must bracket that escape.
See it run
(call/cc (lambda (escape) (escape 'done) 'unreachable))Observed result
doneRead the example
call/cc passes an escape procedure to the lambda. Calling it with done immediately transfers that value to the enclosing call/cc. The later symbol unreachable is never evaluated, and the escape is consumed by this transfer.
dynamic-wind takes three procedures. One for going in, one for the work in the middle, and one for coming out.
(dynamic-wind
(lambda () (display "before "))
(lambda () (display "during "))
(lambda () (display "after")))Observed result
before during afterThe last procedure still runs when an escape in the middle carries you out. That is where cleanup belongs.
How to reason about it
- The first invocation atomically consumes the continuation before transfer, and every later invocation is E340.
dynamic-windrunsbefore, thenthunk, thenafter. Unwinding runs pendingafterthunks innermost first.- A new error or escape from an
afterthunk replaces the signal already in flight.
Choose quickly
| Need | Prefer | Why |
|---|---|---|
| ordinary choice | if or cond | local control stays visible |
| return from a helper | normal procedure result | no non-local transfer |
| leave several nested calls once | call/cc escape | one explicit upward exit |
| cleanup around a possible escape | dynamic-wind | after runs during unwind |
| resume the same point repeatedly | redesign the flow | continuations are one-shot and non-reentrant |
A common mistake
Multi-shot continuation reuse and dynamic-context re-entry are deferred.
Current boundaries
- Host exceptions and host continuations do not implement guest transfer.
Keep going
Conditionals covers ordinary control flow. Diagnostics explains how errors travel along those same explicit interpreter signals.