#error #termination #derive-error #error-handling #derive #debugging

macro thistermination

A library crate to add the Termination trait to error enums inspired by thiserror

2 stable releases

1.1.0 Oct 8, 2023
1.0.0 Oct 5, 2023

#2550 in Rust patterns

MIT license

40KB
492 lines

thistermination

thistermination is a library crate inspired by thiserror to add the std::process::Termination trait to error enums.

[dependencies]
thistermination = "1.0"

Compiler support: requires rustc 1.56+

Why Implement The Termination Trait?

A struct or enum that implements the std::process::Termination and std::fmt::Debug traits can be returned by the main function, allowing developers to print a message on program exit and set the exit code.

Usage

To add the std::process::Termination trait to an enum, you can use one of three possible derive macros:

  • #[derive(Termination)]: is intended to be used in combination with thiserror, this macro implements the traits std::process::Termination and std::fmt::Debug. The exit_code defaults to libc::EXIT_FAILURE, and the Debug message is the same as the Display message unless explicitly set using exit_code and msg.

    use thistermination::{Termination};
    use thiserror::Error;
    
    #[derive(Error, Termination)]
    pub enum RequestError {
        #[error("request failed {0:?}")]
        RequestFailed(#[from] reqwest::Error),
        #[termination(msg("exiting wrong api key"))]
        #[error("wrong api key")]
        WrongAPIKey,
        #[termination(exit_code(3))]
        #[error("failed with status {0}")]
        RequestStatusError(u16),
        #[termination(exit_code(4), msg("exiting failed to load image {error:?}"))]
        #[error("failed to load image {error:?}")]
        ImageLoadError{#[from] error: image::ImageError},
    }
    
    fn main() -> Result<(), RequestError> {
        Err(RequestError::WrongAPIKey)
    }
    
  • #[derive(TerminationFull)]: is intended to be used without thiserror, this macro implements the traits std::process::Termination, std::fmt::Debug, std::fmt::Display, and std::error::Error. The exit_code defaults to libc::EXIT_FAILURE, and msg is required and used for both Display and Debug.

    use thistermination::{TerminationFull};
    
    #[derive(TerminationFull)]
    pub enum RequestError {
        #[termination(exit_code(1), msg("request failed {0:?}"))]
        RequestFailed(#[from] reqwest::Error),
        #[termination(exit_code(2), msg("wrong api key"))]
        WrongAPIKey,
        #[termination(exit_code(3), msg("failed with status {0}"))]
        RequestStatusError(u16),
        #[termination(exit_code(4), msg("failed to load image {error:?}"))]
        ImageLoadError{#[from] error: image::ImageError},
    }
    
    fn main() -> Result<(), RequestError> {
        Err(RequestError::WrongAPIKey)
    }
    
  • #[derive(TerminationNoDebug)]: is the most basic variant, implementing only the std::process::Termination trait. If no exit_code is provided, it defaults to libc::EXIT_FAILURE. However, the std::fmt::Debug trait is necessary for the enum to be returned by the main function and must be implemented manually or using the Debug macro.

    use thistermination::{TerminationNoDebug};
    
    #[derive(TerminationNoDebug, Debug)]
    pub enum RequestError {
        #[termination(exit_code(1))]
        RequestFailed(reqwest::Error),
        WrongAPIKey,
        #[termination(exit_code(3))]
        RequestStatusError(u16),
        ImageLoadError{error: image::ImageError},
    }
    
    fn main() -> Result<(), RequestError> {
        Err(RequestError::WrongAPIKey)
    }
    

Details

  • thistermination does not appear in your public API; the macros simply implement the the various traits.

  • The macros can be derived for unit enums, enums with named fields, and enum tuples.

  • msg supports accessing the fields of the enum in a format string manner

    • #[termination(msg("{var}"))] ⟶ write!("{}", self.var)
    • #[termination(msg("{0}"))] ⟶ write!("{}", self.0)
    • #[termination(msg("{var:?}"))] ⟶ write!("{:?}", self.var)
    • #[termination(msg("{0:?}"))] ⟶ write!("{:?}", self.0)

    You can also specify additional format string arguments for msg

    #[derive(TerminationFull)]
    pub enum RequestError {
        #[termination(exit_code(4), msg("failed to load image {0:?}"))]
        ImageLoadError(#[from] image::ImageError),
    }
    
  • Using #[from] will generate a std::convert::From implementation for the specific variant. A variant with #[from] is not allowed to contain any additional fields and can only be used in combination with #[derive(TerminationFull)].

    #[derive(TerminationFull)]
    pub enum CLIError {
        #[termination(exit_code(4), msg("Invalid argument {0}, expected < {}", i16::MAX))]
        InvalidArgument(u16),
    }
    
  • You can also change the default values of exit_code and msg by adding the #[termination(...)] helper attribute to the enum itself.

    #[derive(Error, Termination)]
    #[termination(exit_code(3), msg("Fatal Error"))]
    pub enum RequestError {
        #[error("request failed {0:?}")]
        RequestFailed(#[from] reqwest::Error),
        #[error("wrong api key")]
        WrongAPIKey,
        #[error("failed with status {0}")]
        RequestStatusError(u16),
        #[termination(exit_code(4), msg("exiting failed to load image {error:?}"))]
        #[error("failed to load image {error:?}")]
        ImageLoadError{#[from] error: image::ImageError},
    }
    

Comparison To thiserror

#[derive(TerminationFull)] can be used instead of thiserror as it offers many of the basic features of thiserror. However, it lacks some features like #[source], #[backtrace], the ability to automatically detect a backtrace, and #[error(transparent)]. If any of these features are required, you can use thiserror in combination with #[derive(Termination)].

Dependencies

~2.5–4MB
~72K SLoC