In the general case, it's impossible. Even ignoring effects from outside of the program (as mentioned by mcarton), whoever compiles the final binary can choose if a panic actually triggers stack unwinding or if it simply aborts the program. In the latter case, there's nothing you can do.
In the case of unwinding panic or normal exit, you can implement Drop
and use the conventional aspects of RAII:
struct Cleanup;
impl Drop for Cleanup {
fn drop(&mut self) {
eprintln!("Doing some final cleanup");
}
}
fn main() {
let _cleanup = Cleanup;
panic!("Oh no!");
}
thread 'main' panicked at 'Oh no!', src/main.rs:12:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Doing some final cleanup
It appears that Java's shutdown hooks allow running multiple pieces of code in parallel in threads. You could do something similar with some small modifications:
use std::{
sync::{Arc, Condvar, Mutex},
thread,
};
#[derive(Debug, Default)]
struct Cleanup {
hooks: Vec<thread::JoinHandle<()>>,
run: Arc<Mutex<bool>>,
go: Arc<Condvar>,
}
impl Cleanup {
fn add(&mut self, f: impl FnOnce() + Send + 'static) {
let run = self.run.clone();
let go = self.go.clone();
let t = thread::spawn(move || {
let mut run = run.lock().unwrap();
while !*run {
run = go.wait(run).unwrap();
}
f();
});
self.hooks.push(t);
}
}
impl Drop for Cleanup {
fn drop(&mut self) {
eprintln!("Starting final cleanup");
*self.run.lock().unwrap() = true;
self.go.notify_all();
for h in self.hooks.drain(..) {
h.join().unwrap();
}
eprintln!("Final cleanup complete");
}
}
fn main() {
let mut cleanup = Cleanup::default();
cleanup.add(|| {
eprintln!("Cleanup #1");
});
cleanup.add(|| {
eprintln!("Cleanup #2");
});
panic!("Oh no!");
}
See also:
shutdown_hooks
usesatexit
, which is about as good as it gets on POSIX systems. It will run on most panics, but not on eg. segfaults. – Hourihan