rust-lang / project-error-handling

Error handling project group
Apache License 2.0
268 stars 18 forks source link

Dealing with process exit codes #22

Open epage opened 3 years ago

epage commented 3 years ago

There are two sides to this

Turning std::process::Command into errors

Providing a way to bubble up process exit codes to main

Alternative:

Exit code prior art:

main prior art:

yaahc commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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.