Exit_safely now also on stable
โ Rust ๐ 2026-06-13 ๐ค surdeus ๐๏ธ 2Hi folks,
Over the last week I've removed all reliance on experimental features, so now you can exit safely whatever toolchain you use to build your app.
From the readme:
exit_safely - with Drop & meaningful exit codes
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 flexibility, zero boilerplate.
Why?
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"
Example on nightly
For the best use in main() you will probably also want to derive Debug and Try
(via try_v2):
#![cfg_attr(unstable_never_type, feature(never_type))]
#![cfg_attr(unstable_try_trait_v2, feature(try_trait_v2))]
#![cfg_attr(unstable_try_trait_v2_residual, feature(try_trait_v2_residual))]
use std::process::Termination as _T;
use exit_safely::Termination;
use try_v2::{Try, Try_ConvertResult};
/// First define your exit codes:
#[derive(Debug, Termination, Try, Try_ConvertResult)]
#[must_use]
#[repr(u8)]
enum Exit<T: _T> {
Ok(T) = 0,
Error(String) = 1,
InvocationError(String) = 2,
}
/// Then any conversion:
/// clap errors return exit_code 2 & output the details
/// to stderr, letting clap handle formatting
impl<T: _T> From<clap::Error> for Exit<T> {
fn from(err: clap::Error) -> Self {
Self::InvocationError(err.to_string())
}
}
fn main() -> Exit<()> {
// Use `?` to return the right exit code for the error type
let cli = Cli::try_parse()?;
// ok_or()? converts a missing value to an exit
let value = inputs
.next()
.ok_or(Exit::Error("Not enough input, need more cheese".to_string()))?;
// if your central processing returns a value which might be invalid
Exit::from(process(inputs)?)
// or simply return `Exit::...`
// Exit::Ok(())
}
Example on stable
If you prefer not to use a nightly toolchain then exit_safely still works fine, although
you cannot leverage the power of ? just yet. The same example looks more like the pattern
if you used std::process::exit (or go)
use std::process::Termination as _T;
use exit_safely::Termination;
/// 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,
}
/// Then any conversion:
/// clap errors return exit_code 2 & output the details
/// to stderr, letting clap handle formatting
impl<T: _T> From<clap::Error> for Exit<T> {
fn from(err: clap::Error) -> Self {
Self::InvocationError(err.to_string())
}
}
fn main() -> Exit<()> {
// Match on `Results` and use `into` to return the right exit code for the error type
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(e) => return e.into(), // or `Exit::from(e)` if you prefer
};
// `let ... else` converts a missing value to an exit
let Some(value) = inputs.next() else {
return Exit::Error("Not enough input, need more cheese".to_string())
};
process(inputs);
Exit::Ok(())
}
Stability
This crate makes use of the following experimental features if they are available but
does not require any experimental features to work.For improved compiler errors
To provide nicer compiler errors with "help", "notes" and a warning if you forget the repr(u8)
On current stable help & notes are output as extra errors, but warnings are not possible
Recommended: try_trait_v2, try_trait_v2_residual & never_type
I find the ergonomics work best for types which also implement the experimental
Tryand
created the cratetry_v2to make it easy for you to
take advantage.I consider all of the above features to be reliable and already well advanced in the
stabilisation process. Nevertheless, I run automated tests every month and on every PR
against stable, beta & nightly to ensure no fundamental changes affect this crate. I get a
direct notification on my phone for any failures.Feel free to check out the build script, workflows and source to see how I do this.
1 post - 1 participant
๐ท๏ธ Rust_feed