failure icon indicating copy to clipboard operation
failure copied to clipboard

Helper for displaying all causes in the entire error chain

Open emk opened this issue 8 years ago • 3 comments

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.

emk avatar Nov 23 '17 18:11 emk

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);
}

withoutboats avatar Nov 28 '17 01:11 withoutboats

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);
}

epage avatar Nov 28 '17 03:11 epage

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.)

lilianmoraru avatar Dec 07 '17 11:12 lilianmoraru