5 ms·
What phoe-krk mentions is indeed the main thing that blocks “real” integration into existing languages with exceptions: they always unwind before executing the
by dasyatidprime 6y ago
What phoe-krk mentions is indeed the main thing that blocks “real” integration into existing languages with exceptions: they always unwind before executing the handler. (JWZ even complained about this while writing about Java.)
To expand on their comment: if `throw` in Java or C++ is similar to `error` in CL (or `throw` in some special cases), a `catch` clause in Java or C++ is equivalent to a label, where the `try` binds a handler that exits to it immediately. There's no equivalent of putting code in the handler other than a single unwind-and-jump, and there's no equivalent of restarts.
In CL, a condition handler gets called on top of the existing stack and can inspect what's going on before choosing where to exit to. Other functions in the call stack can provide alternative exits (restarts), like “continue processing anyway” or “substitute a placeholder value”; these are dynamically named, rather than lexically bound like `catch` . So there's a lot more possible decoupling, at least in theory. The equivalent of `finally`/destructors is `unwind-protect`, which has to interoperate with the condition mechanism but doesn't deal with conditions itself.
In C++ or Java, you could implement the restarts with a (thread-local) stack of restart descriptions plus try/finally or constructor/destructor, and the same for handlers, and then do your nonlocal exits with specialized throwables. I did something similar in Lua, in fact, while trying to extend it into a fancier language. But a “normal” `throw` will bypass all of that. That's not dangerous if you do the unwind-protects properly, but none of your existing libraries will be built for it, and the results will be kind of anemic.
In the Java-style objects+throw/catch world, similar things can be achieved by toggling “what to do if X happens” state or plumbing callback pointers through the object graph beforehand, which is similar but more ad-hoc, and possibly harder to add to existing systems. That said, the CL style proper is very tied to the call stack, which can also make things tricky.
- phoe-krk 6y agoThanks for the assist! I have one comment: > The equivalent of `finally`/destructors is `unwind-protect`, which has to interoperate with the condition mechanism but doesn't deal with conditions itself. It actually doesn't need to interoperate with the condition system; it has to interoperate with the stack-unwinding primitives - `go`/`tagbody`, `return-from`/`block`, and `throw`/`catch` - that are one of the foundations of the condition system. If it works with those, then it works with a condition system, since all control flow that happens inside the condition system is a derivative of those primitive operators.
- dasyatidprime 6y agoAh, yes! I was treating those as effectively “part of” the condition mechanism for the comparative explanation, but you're right that that's misleading in CL. Thanks for the correction, or, shall I say, good catch. :-)
- phoe-krk 6y ago> good catch Ouch. Kudos for the pun, that was truly awful. :D
- oalae5niMiel7qu 6y agoIn C++, you can override the __cxa_throw() function to implement a fully-fledged condition system that calls handlers on top of the stack instead of unwinding first. Call the real __cxa_throw if there's no dynamic handler. To top it off, you can provide a "restart" class that this new __cxa_throw treats like ordinary C++ exceptions, and throw an instance of it to perform the "exit". I have no idea if this hack would comply with the standard, but it works with GCC. You'd be missing COMPUTE-RESTARTS, however, so there'd be no asking the user where to jump.
- phoe-krk 6y agoCould you link me to any sources for that GCC behavior? Also, if we can have dynamically established handlers, then we could also have dynamically established restarts (even if by means of a dynamic variable implemented via a lexical variable + a destructor), and therefore we could have a COMPUTE-RESTARTS of our own and then be able to invoke it arbitrarily as well as invoke individual restarts.
- oalae5niMiel7qu 6y agoThis isn't about GCC, but GCC's behavior seems to be compatible: https://libcxxabi.llvm.org/spec.html https://libcxxabi.llvm.org/spec.html
- louai 6y agoThe Itanium C++ ABI mentions something similar > A two-phase exception-handling model is not strictly necessary to implement C++ language semantics, but it does provide some benefits. For example, the first phase allows an exception-handling mechanism to dismiss an exception before stack unwinding begins, which allows resumptive exception handling (correcting the exceptional condition and resuming execution at the point where it was raised). While C++ does not support resumptive exception handling, other languages do, and the two-phase model allows C++ to coexist with those languages on the stack. http://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html http://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
- 6y ago