General Pattern of Avoiding longjmp() and Exceptions

Historical Rebol would just use longjmp() to cause abrupt exceptions when failing conditions happened. This crossed an arbitrary number of Rebol stacks in an uncooperative fashion. The only places that would catch it would be specific points that did a setjmp(), e.g. the Rebol2 version of "TRY".

The more I got to know longjmp() the more I didn't like it.

Ren-C's stackless nature makes it possible to use setjmp() once around the trampoline, and then any longjmp()-based abrupt failures will only disrupt the stack of the native from which it jumps. The trampoline catches it, and then converts it into a cooperative Rebol-throw-style return result, that bubbles up normally through the call stack.

And also, there's a clever abstraction of the jumping mechanics that allows you to build using C++ exceptions or longjmp()...the code is stylized in a neat way with macros that look natural but can go either way.

But still: there are even some platforms that don't offer either facility. I think in terms of minimal dependencies, being able to perform normal execution even on platforms with no C++ exceptions or longjmp() is an asset.

So this has continued pushing forward a philosophy point that we shouldn't depend on longjmp()/exceptions for anything that would be "normal" operation. Any errors occurring from an "abrupt failure" should basically not be intercepted by anything but "weird" development tools... e.g. the console.

This does mean I'm converting a lot of code like:

 void Do_Some_Stuff(...) {
     if (Something_I_Dont_Like(...))
        fail ("Something happened I don't like");  // uncooperative longjmp()
     ...
     return;
 }

 DECLARE_NATIVE(...) {
     Do_Some_Stuff(...);  // may teleport stack to trampoline, not run next line
     return OUT;
}

...to look like:

 Option(Error*) Trap_Do_Some_Stuff(...) {
     if (Something_I_Dont_Like(...))
        return ERROR("Something happened I don't like");  // returning error
     ...
     return nullptr;
 }

 DECLARE_NATIVE(...) {
     Option(Error*) e = Trap_Do_Some_Stuff(...);
     if (e)
         return FAIL(unwrap e);  // cooperative failure returned to trampoline
     return OUT;
}

At some times I'm a little conflicted over this seeming like make-work -BUT- it isn't. In addition to meaning this code can have error conditions on platforms with no longjmp() or C++ exceptions, you get a clearer picture of which routines think they might fail in normal operations and don't... so as a caller you can act accordingly.

There are still abrupt failures, but they're basically things you would think of as being okay if it crashed the interpreter completely... it's just a convenience that things like the console don't crash. But you should never react to those failures as a way of implementing your program logic.

1 Like