std::process::exit warns:
"Note that because this function never returns, and that it terminates the process, no destructors on the current stack or any other thread’s stack will be run. If a clean shutdown is needed it is recommended to ... simply return a type implementing Termination ... from the main function and avoid this function altogether"
exit_safely provides a simple and highly transparent option to derive(Termination) from your own enum with a very simple API which still provides you full control over exit codes and what to (safely) output to stderr.
Minimal magic, maximum flexibilty, zero boilerplate.
Here's the full example from the integration tests, showing how this plays really nicely with Try (derived via try_v2)
#![feature(never_type)]
#![feature(try_trait_v2)]
use exit_safely::Termination;
use try_v2::*;
use std::io;
use std::process::Termination as _T; // Needed as trait bound for Exit
#[derive(Debug, Termination, Try, Try_ConvertResult)]
#[repr(u8)]
enum Exit<T: _T> {
Ok(T) = 0,
// Any io errors are ExitCode 1, delegate stderr contents to io::Error's Display
FileError(io::Error) = 1,
// ExitCode 2 can be manually created with a String to be sent to stderr
InvocationError(String) = 2,
// Just exit with ExitCode 3 and nothing extra sent to stderr
Other = 3,
}
// Allows for simple `?` propogation of `io::Error`s
impl<T: _T> From<io::Error> for Exit<T> {
fn from(err: io::Error) -> Self {
Exit::FileError(err)
}
}
// And for validation of data you can impl From<foo> for Exit
struct File {
pass_or_fail: String,
contents: String,
}
impl From<File> for Exit<()> {
fn from(file: File) -> Self {
match file.pass_or_fail.as_str() {
"PASS" => Exit::Ok(()),
_ => Exit::Other,
}
}
}
// if given too few args it will fail with ExitCode 2 (InvocationError) and a message
// if given `<FILENAME> <FAIL>` it will fail with ExitCode (Other) 3,
// if given `<FILENAME> <ANYTHING_ELSE>` it will check file exists
fn main() -> Exit<()> {
println!("Hello, world!");
let mut args = std::env::args();
let filename = args
.nth(1)
// `ok_or(Exit::foo)?` will exit safely with ExitCode from `foo`
.ok_or(Exit::InvocationError("REALLY Not enough args".to_string()))?;
let pass_or_fail = args
.next()
.ok_or(Exit::InvocationError("Not enough args".to_string()))?;
// Using `?` on an Error which can convert to Exit will also exit safely with the relevant ExitCode
let contents = std::fs::read_to_string(&filename)?;
let file = File {
pass_or_fail,
contents,
};
// If everything goes well, check the data ...
Exit::from(file)
// Or you could of course just `Exit::Ok(())`
}