Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Dealing with process exit codes #22

Open
epage opened this issue Dec 3, 2020 · 4 comments
Open

Dealing with process exit codes #22

epage opened this issue Dec 3, 2020 · 4 comments

Comments

@epage
Copy link

epage commented Dec 3, 2020

There are two sides to this

Turning std::process::Command into errors

Providing a way to bubble up process exit codes to main

  • Some times a user will want a std::process::Commands exit code to bubble up, sometimes they won't
  • Some times a user will want an std::io::ErrorKind to bubble up and sometimes they won't
  • Certain operations get distinct error code: usage/config/generic error vs special error that a third-party needs to programmatically process

Alternative:

  • Directly calling std::process::exit throughout the business logic. Personally, I've found unexpected forms of exiting a program to lead to "spaghetti code".

Exit code prior art:

  • std::process::ExitCode
    • Only two codes defined
    • Newtype for policy enforcement / targeted extending with traits
  • sysexit
    • Includes common codes and signals
    • Convert std::process::Commands and std::io::ErrorKinds to codes
    • enum for policy enforcement / targeted extending with traits
    • But can't represent states not known a priori
  • exit-code and exitcode
    • Includes common codes as i32 constants
  • proc-exit
    • Includes common codes and signals
    • Convert std::process::Commands and std::io::ErrorKinds to codes
    • Newtype for policy enforcement / targeted extending with traits

main prior art:

  • std::process::Termination
    • A blanket impl makes it difficult for people to provide their own status codes
  • exitfailure
    • Turns Debug into Display for Termination trait
  • proc-exit
    • Turns Debug into Display if choosing to rely on Termination trait
    • Allows "silent" exit (error message got logged previously, so don't print anything on process exit)
    • When not integrating with Termination, provides a way to get custom exit codes to the user
@yaahc
Copy link
Member

yaahc commented Dec 3, 2020

Some ideas:

The generic member access RFC makes it so we can extract exit codes from arbitrary errors if they provide one. If we updated std::process::ExitCode to be able to represent all the exit codes we could return or use with std::process::exit then we could update the Termination implementation on Result to attempt to pull an ExitStatus from the chain of errors (probably requires specialization here, questionable if possible).

Something like this:

#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: std::error::Error> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);
        let exit_code = err.context::<&ExitCode>().unwrap_or(&ExitCode::FAILURE);
        exit_code.report()
    }
}

Even if we don't update the std ExitCode and Termination impls, once Termination is stable people will be able to introduce their own Result equivalent at the top level that has the proper implementation for grabbing exit statuses of any type, even defined by third party libraries, from any type that implements the Error trait, which might be sufficient.

Until Termination is stable people can still use the same approach proc-exit does, and that library could change the interface of exit from fn(ExitResult) -> ! to fn(Result<(), E: Error> -> !) and still manage to find all of its own Code's via the generic member access interface, letting errors with exit codes associated compose nicely with other error types.

@burdges
Copy link

burdges commented Dec 3, 2020

I suggested up #20 largely because it'd make rust's handling of numeric exit codes far more POSIX like but more flexible.

If created from a a numeric error code, then a TinyBox<dyn TinyError> would be [usize; 2] with no allocations. It'd even work without the alloc crate, but still track dynamically the error's original type, and support some downcast methods.

It could then later be promoted to allocation based types that track the backtrace, etc., maybe one could even make this promotion depend upon a alloc feature, but the point is largely to make smaller rust crates more usable when doing things like writing an OS.

I actually noticed that #20 would be useful from seeing zcash's crypto libs use the Read and Write traits for serializations. At this point, arworks/zexe has their own fork of those traits, but it'd still be cool more std::io tooling just worked without std or alloc.

@yaahc
Copy link
Member

yaahc commented Dec 3, 2020

I suggested up #20 largely because it'd make rust's handling of numeric exit codes far more POSIX like but more flexible.

Makes sense and good to know

If created from a a numeric error code, then a TinyBox would be [usize; 2] with no allocations.

fwiw, I think this is also true of ZST error types plus Box, tho im pretty sure you'd still need an alloc dependency even if it doesn't allocate a real pointer.

@burdges
Copy link

burdges commented Dec 3, 2020

fwiw, I think this is also true of ZST error types plus Box, tho im pretty sure you'd still need an alloc dependency even if it doesn't allocate a real pointer.

The idea is the tinybox crate would have an alloc feature. If the alloc feature is enabled then it invokes box for types with alignment or size larger than usize. If the alloc feature is disabled, then tinybox panics for types with alignment or size larger than usize, meaning it still builds but pushes the error into runtime. If a type has alignment and size no larger than usize then tinybox always works with or without the alloc feature.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants