rust-lang-deprecated / failure

Error management
https://rust-lang-nursery.github.io/failure/
Apache License 2.0
1.43k stars 138 forks source link

Helper for displaying all causes in the entire error chain #76

Open emk opened 6 years ago

emk commented 6 years ago

Thank you for digging into this tricky design problem!

I miss the "chained" error display provided by quick_main! in error-chain. A lot of my software has been designed around showing the various nested error messages. For example:

error reading "avatar_01_01.es.srt2": No such file or directory (os error 2)

Showing the full context of the error provides helpful clues to the user. But failure provides no easy way to get that string, and it seems to encourage the use of errors like:

error reading "avatar_01_01.es.srt2"

This is less ergonomic for users.

One workaround is to write something like this:

    // Decide which command to run, and run it, and print any errors.
    if let Err(err) = run(&args) {
        let mut opt_cause = Some(err.cause());
        let mut first = true;
        while let Some(cause) = opt_cause {
            if first {
                first = false;
            } else {
                write!(io::stderr(), ": ")
                    .expect("unable to write error to stderr");
            }
            write!(io::stderr(), "{}", cause)
                .expect("unable to write error to stderr");
            opt_cause = cause.cause();
        }
        write!(io::stderr(), "\n").expect("unable to write error to stderr");
        process::exit(1);
    }

This is potentially related to #41.

I don't think it's necessary to go as far as quick_main!. It might be enough to have a display_with_causes method or even a DisplayWithCauses newtype, allowing us to write something like:

write!(io::stderr(), "{}\n", err.display_with_causes())

Obviously some of this could be provided by an external crate. But there might be some nice ergonomic improvements to be made here.

withoutboats commented 6 years ago

I've just merged #54, which implements a causes iterator. I think something like this would be adequate for your use case for now:

let mut stderr = io::stderr();
for fail in err.causes() {
    let _ = writeln!(stderr, "{}", fail);
}
epage commented 6 years ago

For reference, with #54 here is an approximation (meaning I haven't built it) of error-chain:

// Decide which command to run, and run it, and print any errors.
if let Err(err) = run(&args) {
    let mut stderr = io::stderr();
    let mut causes = err.causes();
    writeln!(stderr, "Error: {}", causes.next().expect("`causes` to at least contain `err`"))
        .expect("unable to write error to stderr");
    for cause in causes {
        writeln!(stderr, "Caused by: {}", cause)
            .expect("unable to write error to stderr");
    }
    // The following assumes an `Error`, use `if let Some(backtrace) ...` for a `Fail`
    writeln!(stderr, "{:?}", backtrace)
        .expect("unable to write error to stderr");
    process::exit(1);
}
lilianmoraru commented 6 years ago

The error-chain is my favorite feature in error-chain. I remember rustup failing to download a file from behind the company's proxy and instead of getting the usual misleading error: No such file or directory, I got a very clear chain of errors explaining that:

  1. It failed to access the link
  2. Because it failed to access the link, it failed to download the file
  3. Because it failed to download the file, it does not exist

It made it extremely easy for me to fix the root cause.

Edit(the rustup output when I remove the SOCKS4 proxy):

error: could not download file from 'https://static.rust-lang.org/rustup/release-stable.toml' to '/tmp/rustup-update.T7Ry8gjuIytx/release-stable.toml'
info: caused by: error during download
info: caused by: [7] Couldn't connect to server (Failed to receive SOCKS4 connect request ack.)