Is there a way for exit() to run destructors?

Currently the exit function in std::process stops the program abruptly without running any destructors first.
Would there be any way to make the exit function run destructors? I know C's stdlib.h has a atexit() function? Although this function can't take any arguments.

Blatant plug: exit_safely - I'm the author, as I had the exact same "WTF-now!" reaction and decided to do something about it for myself ... which turned into doing something about it for myself in a crate I use elsewhere ... which ...

https://crates.io/crates/dtor

But I'm not sure if it will run when explicitly calling exit.

Can you XY this to just stop using exit? ControlFlow in std::ops - Rust with ? can be helpful for this.

Or, in some ways less elegantly but with less "thread it through everything", you could consider struct GracefulExitPlease; with panic_any(GracefulExitPlease) that you catch in main, as then the unwinding will run (non-static) destructors.

If you're going to XY it, you'd be better using Result.
ControlFlow doesn't impl Termination so needs wrapping in a try block and you still need to do something to kill the process and return an error code.
Sadly Result doesn't let you return any error code other than 0/1, which makes for some rather ugly CLI semantics.

/// First define your exit codes:
#[derive(Debug, Termination)]
#[must_use]
#[repr(u8)]
enum Exit<T: _T> {
    Ok(T) = 0,
    Error(String) = 1,
    InvocationError(String) = 2,
}

// Return Exit<()> from main
fn main() -> Exit<()> {
    ...
}

feels nicest to me.

@MusicalNinjaDad @kingwingfly @scottmcm Thanks for the replies! However I do wonder how you could do this if you wanted to exit cleanly from outside main? I am aware that you would probably use panic! if you needed to abruptly exit from a function, but how about in the very rare case when you need to cleanly exit from outside main?

@MusicalNinjaDad why do you use T: _T in your code instead of just T? does it make a difference?

There's no way to safely stop running threads, and threads' state will have objects that won't run destructors until the thread decides to return.

If you care about graceful shutdown, you need to architect your program for it. Make it gracefully stop work and return step by step, all the way to main.

Magic global callbacks will be partial solutions or partial solutions that can sometimes crash or deadlock (this includes C's atexit and pthread_cancel, they're hacky and unsafe in C too).

I had left out

use std::process::Termination as _T;

from the copy-pasting (full example is on crates.io / in the docs)

The Ok type must implement Termination (same as if you return a Result from main)

Wouldn’t it be possible to make an exit function which assumes single-threaded use? That would sort of solve the deadlocking issue right? (Although it would have to be marked unsafe I think?l

Rust doesn't have such assumption anywhere, to the point you could say Rust doesn't support single-threaded programs.

Std process exit is panic without stack unwinding, so it doesn't run drop functions, it is like the wild version of panic

If you want to stop the program but wait all the processing of other threads are done first (to prevent potential inconsistency data), there are some ways :

  • passing code/signal to the main via return value, the main handle calling join the threads the exiting the app after it receives the signal. If you don't want to carry return type everywhere, the signal can be passed via channel from other thread then return without carrying value from the thread, the main then wait the channel and does the rest samely. Or you can also use global atomic variable as the signaler
  • like the other comment, you can also use panic + catch unwind + cross thread signaling. Create custom code struct and optionally a simple macro syntax sugar to wrap spawning + wrapping the spawned thread with dedicated catch unwind, then the main thread is parked, when an unwanted error happen, other thread wakes the main thread + call panic in the current thread. The main thread wake up and run the unwinding handle loop to determine is the panic source from the custom code, if yes it runs whatever logic neccessary to handle your custom additional logic then it returns std proccess exit code to the main return type to make the drop function of any code in main function is also be run

If you want when thread 1 exit, all other threads also have to exit no matter what, even if their processings are not finished yet, then skip the calling .join() in the handler after receiving the signal/panic. Join() will make the main threads wait all other thread to finish properly