Repository: zkat/miette (Rust)

Issue:
Incompatibility to anyhow
From my understanding, one goal of this crate is to be a drop-in replacement for anyhow.

Sadly, while `Report` can be converted to `Box<dyn Diagnostic>`, it can *not* be converted to `Box<dyn StdError>`. This is in contrast to `anyhow`, because `anyhow::Error` *can* be converted to `Box<dyn StdError>`. 

My usecase here is that I am the maintainer of a graceful shutdown library for tokio, which needs to support multiple kinds of user module results, so we opted for user code to return errors in the form of `Into<Box<dyn Error>>`. Sadly, this makes it incompatible with code that returns `miette::Result`.

Here a minimal reproducible example that works with anyhow, but not with miette:

``` Rust
use anyhow::{anyhow as make_error, Result};
//use miette::{miette as make_error, Result};

// User code, which might throw an error
fn produce_error() -> Result<()> {
    Err(make_error!("Error message."))
}

// Library call, similar to what we use to handle errors
fn consume_result<ErrType>(err: std::result::Result<(), ErrType>)
where
    ErrType: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    let err = err.map_err(|e| {
        let converted: Box<dyn std::error::Error + Send + Sync> = e.into();
        converted
    });

    println!("Consumed result: {:?}", err);
}

// Feeding the result of the user code into the library
fn main() {
    consume_result(produce_error())
}
```

Would it be possible to implement `impl<E> From<E> for Box<dyn std::error::Error + Send + Sync> where E: miette::Diagnostic` or `impl From<miette::Report> for Box<dyn std::error::Error + Send + Sync>` or something similar?

Thanks for your work and greetings :)


Discussion:

Patch:
diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs
index e1a8593f..3ae84b6f 100644
--- a/src/eyreish/error.rs
+++ b/src/eyreish/error.rs
@@ -83,6 +83,7 @@ impl Report {
             object_mut: object_mut::<E>,
             object_ref_stderr: object_ref_stderr::<E>,
             object_boxed: object_boxed::<E>,
+            object_boxed_stderr: object_boxed_stderr::<E>,
             object_downcast: object_downcast::<E>,
             object_drop_rest: object_drop_front::<E>,
         };
@@ -106,6 +107,7 @@ impl Report {
             object_mut: object_mut::<MessageError<M>>,
             object_ref_stderr: object_ref_stderr::<MessageError<M>>,
             object_boxed: object_boxed::<MessageError<M>>,
+            object_boxed_stderr: object_boxed_stderr::<MessageError<M>>,
             object_downcast: object_downcast::<M>,
             object_drop_rest: object_drop_front::<M>,
         };
@@ -131,6 +133,7 @@ impl Report {
             object_mut: object_mut::<ContextError<D, E>>,
             object_ref_stderr: object_ref_stderr::<ContextError<D, E>>,
             object_boxed: object_boxed::<ContextError<D, E>>,
+            object_boxed_stderr: object_boxed_stderr::<ContextError<D, E>>,
             object_downcast: context_downcast::<D, E>,
             object_drop_rest: context_drop_rest::<D, E>,
         };
@@ -153,6 +156,7 @@ impl Report {
             object_mut: object_mut::<BoxedError>,
             object_ref_stderr: object_ref_stderr::<BoxedError>,
             object_boxed: object_boxed::<BoxedError>,
+            object_boxed_stderr: object_boxed_stderr::<BoxedError>,
             object_downcast: object_downcast::<Box<dyn Diagnostic + Send + Sync>>,
             object_drop_rest: object_drop_front::<Box<dyn Diagnostic + Send + Sync>>,
         };
@@ -213,6 +217,7 @@ impl Report {
             object_mut: object_mut::<ContextError<D, Report>>,
             object_ref_stderr: object_ref_stderr::<ContextError<D, Report>>,
             object_boxed: object_boxed::<ContextError<D, Report>>,
+            object_boxed_stderr: object_boxed_stderr::<ContextError<D, Report>>,
             object_downcast: context_chain_downcast::<D>,
             object_drop_rest: context_chain_drop_rest::<D>,
         };
@@ -455,6 +460,8 @@ struct ErrorVTable {
     object_ref_stderr: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static),
     #[allow(clippy::type_complexity)]
     object_boxed: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn Diagnostic + Send + Sync + 'static>,
+    #[allow(clippy::type_complexity)]
+    object_boxed_stderr: unsafe fn(Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>,
     object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option<NonNull<()>>,
     object_drop_rest: unsafe fn(Box<ErrorImpl<()>>, TypeId),
 }
@@ -513,6 +520,15 @@ where
     mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e)
 }
 
+// Safety: requires layout of *e to match ErrorImpl<E>.
+unsafe fn object_boxed_stderr<E>(e: Box<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>
+where
+    E: StdError + Send + Sync + 'static,
+{
+    // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
+    mem::transmute::<Box<ErrorImpl<()>>, Box<ErrorImpl<E>>>(e)
+}
+
 // Safety: requires layout of *e to match ErrorImpl<E>.
 unsafe fn object_downcast<E>(e: &ErrorImpl<()>, target: TypeId) -> Option<NonNull<()>>
 where
@@ -711,12 +727,34 @@ impl From<Report> for Box<dyn Diagnostic + Send + Sync + 'static> {
     }
 }
 
+impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
+    fn from(error: Report) -> Self {
+        let outer = ManuallyDrop::new(error);
+        unsafe {
+            // Read Box<ErrorImpl<()>> from error. Can't move it out because
+            // Report has a Drop impl which we want to not run.
+            let inner = ptr::read(&outer.inner);
+            let erased = ManuallyDrop::into_inner(inner);
+
+            // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
+            // the right original type E.
+            (erased.vtable.object_boxed_stderr)(erased)
+        }
+    }
+}
+
 impl From<Report> for Box<dyn Diagnostic + 'static> {
     fn from(error: Report) -> Self {
         Box::<dyn Diagnostic + Send + Sync>::from(error)
     }
 }
 
+impl From<Report> for Box<dyn StdError + 'static> {
+    fn from(error: Report) -> Self {
+        Box::<dyn StdError + Send + Sync>::from(error)
+    }
+}
+
 impl AsRef<dyn Diagnostic + Send + Sync> for Report {
     fn as_ref(&self) -> &(dyn Diagnostic + Send + Sync + 'static) {
         &**self


Test patch:
diff --git a/tests/test_convert.rs b/tests/test_convert.rs
index d43190bb..fade2a85 100644
--- a/tests/test_convert.rs
+++ b/tests/test_convert.rs
@@ -21,3 +21,22 @@ fn test_question_mark() -> Result<(), Box<dyn Diagnostic>> {
     f()?;
     Ok(())
 }
+
+#[test]
+fn test_convert_stderr() {
+    let has_dropped = Flag::new();
+    let error: Report = Report::new(DetectDrop::new(&has_dropped));
+    let box_dyn = Box::<dyn std::error::Error + Send + Sync>::from(error);
+    assert_eq!("oh no!", box_dyn.to_string());
+    drop(box_dyn);
+    assert!(has_dropped.get());
+}
+
+#[test]
+fn test_question_mark_stderr() -> Result<(), Box<dyn std::error::Error>> {
+    fn f() -> Result<()> {
+        Ok(())
+    }
+    f()?;
+    Ok(())
+}