Stream: project-error-handling

Topic: Print stack backtrace when main returns an error?


view this post on Zulip Joshua Nelson (Sep 02 2021 at 16:11):

So, I just wrote this code for work:

fn main() {
    if let Err(err) = main_handling_errors() {
        let mut msg = format!("Error: {}", err);
        for cause in err.chain().skip(1) {
            write!(msg, "\n\nCaused by:\n    {}", cause).unwrap();
        }
        eprintln!("{}", msg);

        let backtrace = err.backtrace().to_string();
        if backtrace != "disabled backtrace" && backtrace != "unsupported backtrace" {
            eprintln!("\nStack backtrace:\n{}", backtrace);
        }
        std::process::exit(1);
    }
}

It was just using fn main() -> anyhow::Result<()> before, but that doesn't print a backtrace or the causes. Is there a way to make this the default for errors somehow? I know libstd can't depend on anyhow, but it should still be able to use the backtrace() method even though it's unstable, right? I tried adding it just now but I don't understand specialization at all:

diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 43c7ec5fad3..1a76584b612 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -316,6 +316,7 @@
 #![feature(slice_internals)]
 #![feature(slice_ptr_get)]
 #![feature(slice_ptr_len)]
+#![feature(specialization)]
 #![feature(staged_api)]
 #![feature(std_internals)]
 #![feature(stdsimd)]
diff --git a/library/std/src/process.rs b/library/std/src/process.rs
index c9b21fcf9c6..6d336c266c6 100644
--- a/library/std/src/process.rs
+++ b/library/std/src/process.rs
@@ -2043,7 +2043,15 @@ fn report(self) -> i32 {
 }

 #[unstable(feature = "termination_trait_lib", issue = "43301")]
-impl<E: fmt::Debug> Termination for Result<!, E> {
+default impl<E: fmt::Debug> Termination for Result<!, E> {
+    fn report(self) -> i32 {
+        let Err(err) = self;
+        eprintln!("Error: {:?}", err);
+        ExitCode::FAILURE.report()
+    }
+}
+
+impl<E: fmt::Debug + crate::error::Error> Termination for Result<!, E> {
     fn report(self) -> i32 {
         let Err(err) = self;
         eprintln!("Error: {:?}", err);
error[E0275]: overflow evaluating the requirement `core::result::Result<!, _>: Termination`
  |
  = help: consider adding a `#![recursion_limit="256"]` attribute to your crate (`std`)
  = note: required because of the requirements on the impl of `Termination` for `core::result::Result<!, _>`
  = note: 127 redundant requirements hidden
  = note: required because of the requirements on the impl of `Termination` for `core::result::Result<!, _>`

so I thought I'd get feedback on the general idea first

view this post on Zulip Joshua Nelson (Sep 02 2021 at 16:13):

(I'm aware that depending on "disabled backtrace" and "unsupported backtrace" is a bad idea, but I didn't see a stable way to get that info)

view this post on Zulip Gary Guo (Sep 02 2021 at 16:51):

Have you tried default fn instead of default impl?

view this post on Zulip Joshua Nelson (Sep 02 2021 at 16:52):

with just that change and nothing else it compiles:

diff --git a/library/std/src/process.rs b/library/std/src/process.rs
index c9b21fcf9c6..542c541d74f 100644
--- a/library/std/src/process.rs
+++ b/library/std/src/process.rs
@@ -2044,13 +2044,22 @@ fn report(self) -> i32 {

 #[unstable(feature = "termination_trait_lib", issue = "43301")]
 impl<E: fmt::Debug> Termination for Result<!, E> {
-    fn report(self) -> i32 {
+    default fn report(self) -> i32 {
         let Err(err) = self;
         eprintln!("Error: {:?}", err);
         ExitCode::FAILURE.report()
     }
 }

but adding the second impl gives error: cannot specialize on trait `error::Error`

view this post on Zulip Joshua Nelson (Sep 02 2021 at 16:53):

adding default impl and default fn, but not the second impl gives a really strange error:

error[E0599]: the method `report` exists for enum `core::result::Result<!, E>`, but its trait bounds were not satisfied
    --> library/std/src/process.rs:2033:42
     |
2033 |             Err(err) => Err::<!, _>(err).report(),
     |                                          ^^^^^^ method cannot be called on `core::result::Result<!, E>` due to unsatisfied trait bounds
     |
    ::: /home/jnelson/src/rust/library/core/src/result.rs:503:1
     |
503  | pub enum Result<T, E> {
     | --------------------- doesn't satisfy `core::result::Result<!, E>: Termination`
     |
     = note: the following trait bounds were not satisfied:
             `core::result::Result<!, E>: Termination`

view this post on Zulip Gary Guo (Sep 02 2021 at 16:58):

If you add #![feature(specialization)], you should be able to add a second impl.

view this post on Zulip Gary Guo (Sep 02 2021 at 16:58):

But that's unsound.

view this post on Zulip Gary Guo (Sep 02 2021 at 17:00):

I wonder if impl<E: Debug + Error + 'static> specialization would be sound (it's okay to use non-specialized impl if any lifetime is involved). But I am not an expert on soundness.

view this post on Zulip Joshua Nelson (Sep 02 2021 at 17:01):

oh, this is with feature(specialization) lol

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:05):

@Joshua Nelson using anyhow::Error in main should print the backtrace and sources

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:08):

Is there a way to make this the default for errors somehow?

so the issue is that the Termination impl in std for Result depends on Debug when it prints the Err variant

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:09):

and that impl isn't a universally applicable impl

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:10):

I can't remember the exact explanation I got from Niko before but the current implementation of specialization can't specialize a generic impl with another generic impl

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:10):

the issue being that these trait impls can depend on lifetimes

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:11):

and that causes soundness issues when specializing

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:11):

so we cannot specialize Termination until we have a work around for that issue

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:11):

https://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/#the-soundness-problem goes in detail on the problem

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:12):

but despite this anyhow::Error _should_ do exactly what you want

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:12):

it's Debug impl prints the actual error report

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:12):

including the backtrace and sources

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:13):

so all you have to do is use a type that prints the actual report you want to see as it's Debug impl or you need to replace the Result in the return type to your own custom Result alternative that doesn't depend on Debug

view this post on Zulip Josh Triplett (Sep 02 2021 at 18:17):

Just to confirm something, is the goal here to make it possible to arrange for the backtrace to be printed from the error returned from main, or to do so by default? The latter seems problematic, as it would make default error handling much noisier; sometimes people want to return an error from main as their primary error handling for a CLI program, and those errors aren't programming errors that need a backtrace.

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:20):

Josh Triplett said:

Just to confirm something, is the goal here to make it possible to arrange for the backtrace to be printed from the error returned from main, or to do so by default? The latter seems problematic, as it would make default error handling much noisier; sometimes people want to return an error from main as their primary error handling for a CLI program, and those errors aren't programming errors that need a backtrace.

i don't think it could even be enabled by default, since the backtrace captured from after main isn't going to have any useful context

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:20):

at most we could enable displaying backtraces available through the Error trait when available

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:20):

which I think could still be argued against

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:21):

since arguably return from main is almost always intended to be user facing rather than developer facing

view this post on Zulip Jane Lusby (Sep 02 2021 at 18:21):

and backtraces aren't necessarily the most helpful information to show to users

view this post on Zulip Joshua Nelson (Sep 02 2021 at 18:56):

Ideally it would be on if RUST_BACKTRACE is set in the environment, without the developer having to opt in. I agree it shouldn't be on by default.

view this post on Zulip Joshua Nelson (Sep 02 2021 at 18:57):

Joshua Nelson using anyhow::Error in main should print the backtrace and sources

That was not happening for me. I'm not sure why not.

view this post on Zulip Joshua Nelson (Sep 02 2021 at 18:58):

Oh, I may have needed to enable the backtrace feature in anyhow.

view this post on Zulip Gus Wynn (Sep 02 2021 at 19:11):

Joshua Nelson said:

Joshua Nelson using anyhow::Error in main should print the backtrace and sources

That was not happening for me. I'm not sure why not.

Last I checked, I believe you have to be on nightly (or BOOTSTRAP), and have RUST_LIB_BACKTRACE=1 or RUST_BACKTRACE=1

view this post on Zulip Gus Wynn (Sep 02 2021 at 19:14):

oh wait, the backtrace feature uses backtrace-rs? I didnt know backtrace-rs worked on non-nightly rust

view this post on Zulip Gus Wynn (Sep 02 2021 at 19:16):

I was under the understanding that once backtrace was stabilized, that all errors would act like anyhow (when RUST_BACKTRACE=1), but reading the docs, upon reading the docs that is not true, hmm interesting


Last updated: Jan 26 2022 at 08:21 UTC