Skip to content

MusicalNinjaDad/exit_safely

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exit rust apps safely

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.

Example

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(())`
}

About

Exit rust apps safely

Resources

License

Stars

Watchers

Forks

Contributors