Repository: dtolnay/anyhow
Pull request #42 (2019-11-16 05:04:44)

Add and document {:#} and {:#?} alt format representations
From the new documentation:

> ---
>
> ### Display representations
>
> When you print an error object using "{}" or to_string(), only the outermost underlying error or context is printed, not any of the lower level causes. This is exactly as if you had called the Display impl of the error from which you constructed your anyhow::Error.
>
> ```console
> Failed to read instrs from ./path/to/instrs.json
> ```
>
> To print causes as well using anyhow's default formatting of causes, use the alternate selector "{:#}".
>
> ```console
> Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)
> ```
>
> The Debug format "{:?}" includes your backtrace if one was captured. Note that this is the representation you get by default if you return an error from `fn main` instead of printing it explicitly yourself.
>
> ```console
> Error: Failed to read instrs from ./path/to/instrs.json
>
> Caused by:
>     No such file or directory (os error 2)
>
> Stack backtrace:
>    0: <E as anyhow::context::ext::StdError>::ext_context
>              at /git/anyhow/src/backtrace.rs:26
>    1: core::result::Result<T,E>::map_err
>              at /git/rustc/src/libcore/result.rs:596
>    2: anyhow::context::<impl anyhow::Context<T,E> for core::result::Result<T,E>>::with_context
>              at /git/anyhow/src/context.rs:58
>    3: testing::main
>              at src/main.rs:5
>    4: std::rt::lang_start
>              at /git/rustc/src/libstd/rt.rs:61
>    5: main
>    6: __libc_start_main
>    7: _start
> ```
>
> To see a conventional struct-style Debug representation, use "{:#?}".
>
> ```console
> Error {
>     context: "Failed to read instrs from ./path/to/instrs.json",
>     source: Os {
>         code: 2,
>         kind: NotFound,
>         message: "No such file or directory",
>     },
> }
> ```
>
> If none of the built-in representations are appropriate and you would prefer to render the error and its cause chain yourself, it can be done something like this:
>
> ```rust
> use anyhow::{Context, Result};
>
> fn main() {
>     if let Err(err) = try_main() {
>         eprintln!("ERROR: {}", err);
>         err.chain().skip(1).for_each(|cause| eprintln!("because: {}", cause));
>         std::process::exit(1);
>     }
> }
>
> fn try_main() -> Result<()> {
>     ...
> }
> ```


Patch:
diff --git a/src/context.rs b/src/context.rs
index 3bcff4d..267b7f4 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -102,7 +102,10 @@ where
     E: Debug,
 {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}\nCaused by: {:?}", self.context, self.error)
+        f.debug_struct("Error")
+            .field("context", &self.context.to_string())
+            .field("source", &self.error)
+            .finish()
     }
 }
 
diff --git a/src/error.rs b/src/error.rs
index a3a0160..faa39d2 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -418,15 +418,15 @@ impl DerefMut for Error {
     }
 }
 
-impl Debug for Error {
+impl Display for Error {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        self.inner.debug(formatter)
+        self.inner.display(formatter)
     }
 }
 
-impl Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.inner.error())
+impl Debug for Error {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        self.inner.debug(formatter)
     }
 }
 
@@ -652,16 +652,16 @@ impl<E> ErrorImpl<E> {
 }
 
 impl ErrorImpl<()> {
-    fn error(&self) -> &(dyn StdError + Send + Sync + 'static) {
+    pub(crate) fn error(&self) -> &(dyn StdError + Send + Sync + 'static) {
         unsafe { &*(self.vtable.object_ref)(self) }
     }
 
-    fn error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
+    pub(crate) fn error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
         unsafe { &mut *(self.vtable.object_mut)(self) }
     }
 
     #[cfg(backtrace)]
-    fn backtrace(&self) -> &Backtrace {
+    pub(crate) fn backtrace(&self) -> &Backtrace {
         // This unwrap can only panic if the underlying error's backtrace method
         // is nondeterministic, which would only happen in maliciously
         // constructed code.
@@ -671,48 +671,11 @@ impl ErrorImpl<()> {
             .expect("backtrace capture failed")
     }
 
-    fn chain(&self) -> Chain {
+    pub(crate) fn chain(&self) -> Chain {
         Chain {
             next: Some(self.error()),
         }
     }
-
-    fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        writeln!(f, "{}", self.error())?;
-
-        let mut chain = self.chain().skip(1).enumerate().peekable();
-        if let Some((n, error)) = chain.next() {
-            write!(f, "\nCaused by:\n    ")?;
-            if chain.peek().is_some() {
-                write!(f, "{}: ", n)?;
-            }
-            writeln!(f, "{}", error)?;
-            for (n, error) in chain {
-                writeln!(f, "    {}: {}", n, error)?;
-            }
-        }
-
-        #[cfg(backtrace)]
-        {
-            use std::backtrace::BacktraceStatus;
-
-            let backtrace = self.backtrace();
-            match backtrace.status() {
-                BacktraceStatus::Captured => {
-                    writeln!(f, "\n{}", backtrace)?;
-                }
-                BacktraceStatus::Disabled => {
-                    writeln!(
-                        f,
-                        "\nBacktrace disabled; run with RUST_LIB_BACKTRACE=1 environment variable to display a backtrace"
-                    )?;
-                }
-                _ => {}
-            }
-        }
-
-        Ok(())
-    }
 }
 
 impl<E> StdError for ErrorImpl<E>
diff --git a/src/fmt.rs b/src/fmt.rs
new file mode 100644
index 0000000..60f4a5c
--- /dev/null
+++ b/src/fmt.rs
@@ -0,0 +1,62 @@
+use crate::error::ErrorImpl;
+use std::fmt::{self, Debug};
+
+impl ErrorImpl<()> {
+    pub(crate) fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.error())?;
+
+        if f.alternate() {
+            for cause in self.chain().skip(1) {
+                write!(f, ": {}", cause)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if f.alternate() {
+            return Debug::fmt(self.error(), f);
+        }
+
+        writeln!(f, "{}", self.error())?;
+
+        let mut chain = self.chain().skip(1).enumerate().peekable();
+        if let Some((n, error)) = chain.next() {
+            write!(f, "\nCaused by:\n    ")?;
+            if chain.peek().is_some() {
+                write!(f, "{}: ", n)?;
+            }
+            writeln!(f, "{}", error)?;
+            for (n, error) in chain {
+                writeln!(f, "    {}: {}", n, error)?;
+            }
+        }
+
+        #[cfg(backtrace)]
+        {
+            use std::backtrace::BacktraceStatus;
+
+            let backtrace = self.backtrace();
+            match backtrace.status() {
+                BacktraceStatus::Captured => {
+                    let mut backtrace = backtrace.to_string();
+                    if backtrace.starts_with("stack backtrace:") {
+                        // Capitalize to match "Caused by:"
+                        backtrace.replace_range(0..1, "S");
+                    }
+                    write!(f, "\n{}", backtrace)?;
+                }
+                BacktraceStatus::Disabled => {
+                    writeln!(
+                        f,
+                        "\nStack backtrace:\n    Run with RUST_LIB_BACKTRACE=1 env variable to display a backtrace"
+                    )?;
+                }
+                _ => {}
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 1b96a9b..6be7cab 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -171,6 +171,7 @@
 mod backtrace;
 mod context;
 mod error;
+mod fmt;
 mod kind;
 mod macros;
 
@@ -194,6 +195,88 @@ pub use anyhow as format_err;
 ///   error type does not provide one.
 /// - `Error` is represented as a narrow pointer &mdash; exactly one word in
 ///   size instead of two.
+///
+/// <br>
+///
+/// # Display representations
+///
+/// When you print an error object using "{}" or to_string(), only the outermost
+/// underlying error or context is printed, not any of the lower level causes.
+/// This is exactly as if you had called the Display impl of the error from
+/// which you constructed your anyhow::Error.
+///
+/// ```console
+/// failed to read instrs from ./path/to/instrs.jsox
+/// ```
+///
+/// To print causes as well using anyhow's default formatting of causes, use the
+/// alternate selector "{:#}".
+///
+/// ```console
+/// failed to read instrs from ./path/to/instrs.jsox: No such file or directory (os error 2)
+/// ```
+///
+/// The Debug format "{:?}" includes your backtrace if one was captured. Note
+/// that this is the representation you get by default if you return an error
+/// from `fn main` instead of printing it explicitly yourself.
+///
+/// ```console
+/// Error: failed to read instrs from ./path/to/instrs.jsox
+///
+/// Caused by:
+///     No such file or directory (os error 2)
+///
+/// Stack backtrace:
+///    0: <E as anyhow::context::ext::StdError>::ext_context
+///              at /git/anyhow/src/backtrace.rs:26
+///    1: core::result::Result<T,E>::map_err
+///              at /git/rustc/src/libcore/result.rs:596
+///    2: anyhow::context::<impl anyhow::Context<T,E> for core::result::Result<T,E>>::with_context
+///              at /git/anyhow/src/context.rs:58
+///    3: testing::main
+///              at src/main.rs:5
+///    4: std::rt::lang_start
+///              at /git/rustc/src/libstd/rt.rs:61
+///    5: main
+///    6: __libc_start_main
+///    7: _start
+/// ```
+///
+/// To see a conventional struct-style Debug representation, use "{:#?}".
+///
+/// ```console
+/// Error {
+///     context: "failed to read instrs from ./path/to/instrs.jsox",
+///     source: Os {
+///         code: 2,
+///         kind: NotFound,
+///         message: "No such file or directory",
+///     },
+/// }
+/// ```
+///
+/// If none of the built-in representations are appropriate and you would prefer
+/// to render the error and its cause chain yourself, it can be done something
+/// like this:
+///
+/// ```
+/// use anyhow::{Context, Result};
+///
+/// fn main() {
+///     if let Err(err) = try_main() {
+///         eprintln!("ERROR: {}", err);
+///         err.chain().skip(1).for_each(|cause| eprintln!("because: {}", cause));
+///         std::process::exit(1);
+///     }
+/// }
+///
+/// fn try_main() -> Result<()> {
+///     # const IGNORE: &str = stringify! {
+///     ...
+///     # };
+///     # Ok(())
+/// }
+/// ```
 pub struct Error {
     inner: ManuallyDrop<Box<ErrorImpl<()>>>,
 }
@@ -217,6 +300,7 @@ pub struct Error {
 ///     None
 /// }
 /// ```
+#[derive(Clone)]
 pub struct Chain<'a> {
     next: Option<&'a (dyn StdError + 'static)>,
 }


Test patch:
diff --git a/tests/test_fmt.rs b/tests/test_fmt.rs
index 9d62cea..6d0f775 100644
--- a/tests/test_fmt.rs
+++ b/tests/test_fmt.rs
@@ -1,7 +1,8 @@
 use anyhow::{bail, Context, Result};
+use std::io;
 
 fn f() -> Result<()> {
-    bail!("oh no!");
+    bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
 }
 
 fn g() -> Result<()> {
@@ -12,10 +13,23 @@ fn h() -> Result<()> {
     g().context("g failed")
 }
 
+const EXPECTED_ALTDISPLAY_F: &str = "\
+oh no!\
+";
+
+const EXPECTED_ALTDISPLAY_G: &str = "\
+f failed: oh no!\
+";
+
+const EXPECTED_ALTDISPLAY_H: &str = "\
+g failed: f failed: oh no!\
+";
+
 const EXPECTED_DEBUG_F: &str = "\
 oh no!
 
-Backtrace disabled; run with RUST_LIB_BACKTRACE=1 environment variable to display a backtrace
+Stack backtrace:
+    Run with RUST_LIB_BACKTRACE=1 env variable to display a backtrace
 ";
 
 const EXPECTED_DEBUG_G: &str = "\
@@ -24,7 +38,8 @@ f failed
 Caused by:
     oh no!
 
-Backtrace disabled; run with RUST_LIB_BACKTRACE=1 environment variable to display a backtrace
+Stack backtrace:
+    Run with RUST_LIB_BACKTRACE=1 env variable to display a backtrace
 ";
 
 const EXPECTED_DEBUG_H: &str = "\
@@ -34,7 +49,38 @@ Caused by:
     0: f failed
     1: oh no!
 
-Backtrace disabled; run with RUST_LIB_BACKTRACE=1 environment variable to display a backtrace
+Stack backtrace:
+    Run with RUST_LIB_BACKTRACE=1 env variable to display a backtrace
+";
+
+const EXPECTED_ALTDEBUG_F: &str = "\
+Custom {
+    kind: PermissionDenied,
+    error: \"oh no!\",
+}\
+";
+
+const EXPECTED_ALTDEBUG_G: &str = "\
+Error {
+    context: \"f failed\",
+    source: Custom {
+        kind: PermissionDenied,
+        error: \"oh no!\",
+    },
+}\
+";
+
+const EXPECTED_ALTDEBUG_H: &str = "\
+Error {
+    context: \"g failed\",
+    source: Error {
+        context: \"f failed\",
+        source: Custom {
+            kind: PermissionDenied,
+            error: \"oh no!\",
+        },
+    },
+}\
 ";
 
 #[test]
@@ -42,6 +88,13 @@ fn test_display() {
     assert_eq!("g failed", h().unwrap_err().to_string());
 }
 
+#[test]
+fn test_altdisplay() {
+    assert_eq!(EXPECTED_ALTDISPLAY_F, format!("{:#}", f().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDISPLAY_G, format!("{:#}", g().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDISPLAY_H, format!("{:#}", h().unwrap_err()));
+}
+
 #[test]
 #[cfg_attr(not(backtrace), ignore)]
 fn test_debug() {
@@ -49,3 +102,10 @@ fn test_debug() {
     assert_eq!(EXPECTED_DEBUG_G, format!("{:?}", g().unwrap_err()));
     assert_eq!(EXPECTED_DEBUG_H, format!("{:?}", h().unwrap_err()));
 }
+
+#[test]
+fn test_altdebug() {
+    assert_eq!(EXPECTED_ALTDEBUG_F, format!("{:#?}", f().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDEBUG_G, format!("{:#?}", g().unwrap_err()));
+    assert_eq!(EXPECTED_ALTDEBUG_H, format!("{:#?}", h().unwrap_err()));
+}