How to unwind stack like C setjmp/longjmp or C++ throw

Perhaps, after you've been programming as long as I have-- (since 1979 when I coded z80 assembler on my TRS-80 I got after painting my parents' cottage, lol)-- you learn what you thought was abnormal at one time can surprise you in its normality once you run into the problems that make it the elegant solution. I tend to believe it is the problems you solve that dictate normality, not the strategies you use to solve them.

A point where you make a call into a deeply recursive function where it is known that only stack allocations (i.e. not heap) will occur represents a point in the code to which it may be desirable to return control when a certain condition is met and at an arbitrary depth in the recursive call tree. And you may need to do so as efficiently as possible, and avoid the overhead of a return and frame adjustment at each call point. Hypothetically speaking now, as a formal part of the language, it's possible that an artifact could be introduced (analogous to lifetimes) with which the compiler could make use for generating an optimal execution path. (I'm not proposing this, but I introduce it as just a thought experiment to illustrate an idea.)

To gain some historical perspective it's useful to consider Lisp, which has been around longer than C and many constructs that modern languages like to boast as their best features were first implemented in Lisp. To that end we find that common Lisp (CL) has a longjmp like construct called "tag-body and throw" which solves the problem I mention elegantly. Having to unwind the stack in order to restore (or rollback) the execution flow is a real problem, worth solving and optimizing for, and is far more than what you are characterizing as the "mother of all gotos."

That said, Prolog, as a fundamental part of the language has a built-in feature called back tracking which automatically generates "return points" to which execution must resume more efficiently than an unwind of each call would be desirable. This is, again, because it solves a problem produced by deep recursion.

Rust declares itself as a systems programming language and that says to me (for good or bad) that I would ideally like to use it to replace C in all cases. Therefore the problem landscape is very coarse and offers many hurdles to which the best solution, you may, at first glance, deem abnormal, but later a reasonable one.

I may be wrong of course, but if in doubt I would prefer to do what I can do in C rather than some other-- perhaps more "normally" viewed conventional approach that could be more expensive performance-wise. My current focus is Genetic Programming, after than I hope to create a WAM (Prolog abstract machine) in Rust and ejecting stack accumulated artifacts more efficiently than a call-by-call rewiind will likely be a "problem" to solve at some point.

Quite a newbie then :slight_smile:

What then is the problem we are trying to solve? Sounds like the desire to make a call like:

    let result = back_hole(param1, param2, ...)

Where black_hole can run down all kinds of recursion, allocate all kinds of memory and spawn all kinds of threads, claim all kinds of resources, on its way to the singularity at the bottom.

At which point it should hit "bail out". And magically return to the statement after the call, as if nothing had happened. Perhaps with some kind of result.

In assembler that is easy, just a "JMP" instruction gets you back. Likely with a lot of chaos left behind. In C that is easy with setjmp/longhmp, also likely with a lot of chaos left behind. C++ gives us exceptions.

All of them make reasoning about what a program does tricky.

Lol--- I often envy the newbies!

In my case currently, your "black_hole" (is that what you meant to call it and not back_hole?-- I like that!) would be great. I had thought a similar idea actually as a way to "tag" your call so that a "bail" is possible. (Not the mother of all gotos, but the mother of all returns!)

I don't know if that captures all the cases that the Lisp tag-body and throw / setjmp/ longjmp captures, but it's better than a goto that's for sure. (I agree gotos-- simple branches without meta information are bad and no doubt would quickly lead to spaghetti code!)

Yes, "black hole" ... "singularity", get it? Your alternative sounds horribly obscene.

Oddly enough, since I started programming, in 1974, I have never felt the need for that black hole and bail out. Not setjmp/longjmp, not exceptions, whatever.

What I have often wanted is for a way for threads to be able to instantly commit suicide or be killed off as if nothing had ever happened.

Perhaps that is logically the same problem...

Are the details of this documented anywhere? (Not looking to utilize it, just curiosity).

This may be a cultural difference, and I certainly can't speak for @CKait here. setjmp and friends are certainly a very ad-hoc way to approach it, but there are languages out there with control-flow primitives that accomplish similar goals and where the culture around the language engages with them as a way to construct and explore new structured control flows.

I'm thinking particularly of call/cc here, and its less-powerful delimited-continuation equivalents, which are widely used in Scheme for expressing computations that may complete abruptly. It's rare for code to use those primitives directly, though - the pattern is usually to build a higher-level flow abstraction ("exceptions," say) on top of them, and then to call that from application code.

Rust isn't easily amenable to that kind of control-flow abstraction, in large part because Drop is hard to express in those terms, but it's not unreasonable to want to explore alternatives to Rust's existing "return or panic" rules.

This is due to SEH. Windows has a unified way of handling unwinding, and Rust panics, C++ exceptions, and longjmp hook into the same thing.

1 Like