Past, present, and future meaning of EXIT

When C programmers think of EXIT, they think of something particularly severe...the termination of processes with an exit code:

The idea of EXIT meaning something "big" is also cultural, with exit signs being where you go in an emergency:

Yet Rebol2 chose to use EXIT as the 0-arity form of RETURN. So it was synonymous with return (). This seemed a bit pedestrian for such a word.


At one time I suggested that EXIT be the arity-1 form of QUIT, e.g. exit 1 would act kind-of-like quit/with 1. But unlike QUIT, it would not use the throw mechanics and be interceptible, it would really mean exit the process...no questions asked. This would be something of an homage to C, and I felt it would seem natural to programmers...since the "return" sense felt like a mismatch for the word.

So when it came time to decide the 0-arity definitional version of RETURN for a PROCEDURE (with no result, as opposed to a FUNCTION), leave was chosen over exit, despite being less compatible. "Leaving" sounded more benign, and on par with "Returning".

But 0-arity EXIT hung around. Partially because in those days being fully incompatible without a "shim" (e.g. do <r3-legacy>) was controversial. But also, it was adopted as a non-definitional way of exiting whatever function was running. It got a /WITH refinement so it could exit whatever function was running with a value:

foo: does [
    print if true [
        exit/with "hello"
    ]
    print "world"
]
foo

So the above would print "hello" and "world", because EXIT will terminate the frame of the IF...having no specific relationship to the FUNCTION! that foo holds.

EXIT thus became very similar to something recently introduced as END... though END is a common noun-like variable name (for a variable holding an ending index), while EXIT is not.

The main use of EXIT eventually came via its /FROM refinement, which was the ability to specify exactly which frame to exit. By doing so, it offered the functionality to implement RETURN.

foo: make function! [[x <local> return] [
    return: make function! [[value] [exit/from/with (context of x) :value]]
    if true [
        return x + 1000
    ]
    304 ;-- never gets here
 ]]
 1020 = foo 20

But this meant that pretty much any EXIT had a /WITH, making the arity-0 form nearly useless (and as mentioned above, covered by END).


Years have passed, and I still strongly feel EXIT is not a good name for what LEAVE does today in a PROC/PROCEDURE. EXIT has too much "baggage". Its current purpose for implementing definitional returns would probably be served better by something called JUMP, because it is effectively jumping up the stack the way C's longjmp() might...and I think that would be more understandable as an arity-1 function of where to jump...that had a /WITH refinement for what value to give it.

Hence I think my bias is for 0-arity EXIT to go away, and await reuse. Perhaps it should indeed be a way to hard exit a process without submitting it as a THROW, the way QUIT does. (If you quit from within a DO of a script, it only quits that script, the caller keeps running... @szeng had to add EXIT-REBOL when this functionality is needed.)

More years have passed I'm feeling ever more confident that EXIT likely should mean hard exit to shell; with no opportunity to intercept it.

I was thinking about this question because I couldn't remember how QUIT took its value parameter. Then I realized it uses <end>-ability. If you don't want QUIT to take a parameter, just don't put anything after it.

if whatever [quit]  ; default (e.g. NULL to enclosing script, or 0 to shell) 
if whatever-else [quit 1]

That's the same technique used by RETURN and CONTINUE now (which is why LEAVE and PROC/PROCEDURE are gone, as is CONTINUE/WITH).

Note: BREAK/WITH is gone for a completely different reason, which is the "loop result protocol"... loops return NULL if-and-only-if they BREAK