When users write:
defer:
remove_temporar_files_and_other_cleanups
rest_of_code
(or the equivalent with try/finally), the expectation is that remove_temporar_files_and_other_cleanups will be called upon exiting this function.
However, this isn't the case if quit (+friends) gets called in some code path, as that function is noreturn and (IIRC) would exit immediately.
eg:
proc evil() = quit "err"
proc test() =
defer:
echo "BUG:never called..."
evil()
test()
Not running cleanup code can be really bad; eg beyond temp file cleanup, not flushing files (so you thought you wrote to a file but flushing didn't occur, see example of this problem here: https://stackoverflow.com/a/30251056/1426932), not calling "close connection" api's not automatically handled by OS when quitting a process, etc.
Unfortunately, quit is being used a lot in Nim codebase. For a concrete example where cleanup of temporary files doesn't happen, see: https://github.com/nim-lang/Nim/commit/6de52d2b747074e993707adca2f74143134ce127
There are several ways, but first you need to understand why object cleanup is important, and hence the reason std::exit is marginalized among C++ programmers.
What happens if you don't get to the destructor to flush and close the file? Who knows! But possibly it won't write all the data it was supposed to write into the file.
Sometimes, we really need to quit immediately, however IMO most of the time we should use an alternative that doesn't bypass cleanups, eg: doAssert(false, "mg");
however doAssert doesn't always fit the bill since it doesn't allow customizing error code (and generates a stacktrace if compiled w debug info which may not be wanted in some apps);
so my suggestion is:
proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} # issue a WARNING when used from main (but don't deprecate, there are useful rare cases)
type QuitFailure : Exception # or, if my RFC https://github.com/nim-lang/Nim/issues/8237 is implemented, `type QuitFailure : Error`
errorcode:int
proc quitClean*(errormsg: string, errorcode = QuitFailure): auto=
var e: ref QuitError
new(e)
e.msg = errormsg
e.errorcode = errorcode
raise e
and replace Nim's code generation to wrap main into a top-level try/catch:
proc main()=
try:
mainAux()
except QuitFailure as e:
echo e.msg
return e.errorcode
return 0
addQuitProc has all the same problem as quit, just a bit better by allowing some callbacks to be called. It still ignores all the destructors, defer clauses, exception handlers along the way. So an application using addQuitProc would need to be aware of all the points where these are being used.
The solution I suggested seems simpler. One.
Well, I don't think quit should be used in production applications at all, much like System.exit() in java. If you want to call all the destructors and exception handlers cleanly, you have to unwind all the stack, and I think the only reliable way to do this is to actually exit your program by following the normal code path (just design a path that leads to the end of your main function).
I don't think there is a reliable way to call all handlers and destructors with a privileged code path that avoids actually executing the program.
So an application using addQuitProc would need to be aware of all the points where these are being used.
IMHO, Allocating resource without being aware of deallocation is quite harmful.
Agree with @andrea about its usage, quit helpful for some command line tool where it needs to be sure the user passing a correct argument to it.
--help
and all that needs to happen is printing the help string and exiting?