Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Box<dyn Error> implement Error?

Tags:

rust

Why doesn't Box<dyn Error> implement Error?

I am trying to use the context() method from anyhow on a Result<surf::Response, surf::Error>, and it's not cooperating:

let documents = surf::post("http://10.11.99.1/documents/")
    .await
    .map_err(Box::<dyn std::error::Error + Send + Sync + 'static>::from)
    .context("could not query device")?;

Even with this ugly cast, it still won't work, because surf::Error doesn't implement Error, and neither does Box<dyn Error>.

like image 449
laptou Avatar asked Dec 04 '20 21:12

laptou


1 Answers

Indeed it doesn't; which is highly surprising to people, including me when I originally learned of it:

fn impls_error<T: std::error::Error>() {}
impls_error::<Box<dyn std::error::Error>>();
error[E0277]: the size for values of type `dyn std::error::Error` cannot be known at compilation time
 --> src/main.rs:3:5
  |
2 |     fn impls_error<T: std::error::Error>() {}
  |                       ----------------- required by this bound in `impls_error`
3 |     impls_error::<Box<dyn std::error::Error>>();
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `dyn std::error::Error`
  = note: required because of the requirements on the impl of `std::error::Error` for `Box<dyn std::error::Error>`

The short rationale is listed in the Rust issue tracking this (#60759):

Unfortunately, extending the impl<T: Error> Error for Box<T> to also apply for T: ?Sized fails because then the impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> starts overlapping with impl<T> From<T> for T.

The long rationale is that Rust has this blanket implementation:

impl<T: Error> Error for Box<T> {}

This generic type has an implicit Sized bound, so it could be equivalently written as:

impl<T: Error + Sized> Error for Box<T> {}

If you try to apply this implementation to Box<dyn Error>, then T would need to equal dyn Error. The compiler disallows that because dyn Error by itself is unsized.

The next step you'd take to fix this would be to loosen the bound to allow ?Sized types:

impl<T: Error + ?Sized> Error for Box<T> {}

However, if you do this, then you'll end up with conflicting implementations of the From trait's blanket implementation and the conversion of an concrete error to a Box<dyn Error>:

impl<T> From<T> for T {} // #1

impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> {} // #2

If we could have both of these implementations in play at the same time, then it would become ambiguous what should happen for this code:

let a: Box<dyn Error> = todo!();
let b: Box<dyn Error> = a.into();

Should it leave it as is (following impl 1) or box it again (following impl 2)? Chances are that most people would want 1, but both paths are valid. That's the long form answer as to why Box<dyn Error> cannot implement Error itself at this point in time.

In the future, specialization might allow for both of these to overlap and pick a specific case (probably 1, I'd guess).

See also:

  • How is there a conflicting implementation of `From` when using a generic type?

SNAFU, my competitor to anyhow, also faced the same problem. There, we introduced a trait to help work around the issue. It might be easiest to see what the Snafu macro expands to (cleaned up a bit):

#[derive(Debug, Snafu)]
struct Demo {
    source: Box<dyn std::error::Error>,
}

becomes

impl Error for Demo {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        use snafu::AsErrorSource;
        match *self {
            Self { ref source, .. } => Some(source.as_error_source()),
        }
    }
}

This way, we don't need to rely on source actually implementing Error itself, but instead have a number of concrete implementations that don't overlap with the blanket implementation:

impl AsErrorSource for dyn Error + 'static
impl AsErrorSource for dyn Error + Send + 'static
impl AsErrorSource for dyn Error + Sync + 'static
impl AsErrorSource for dyn Error + Send + Sync + 'static
impl<T: Error + 'static> AsErrorSource for T

95% of the credit for this solution goes to kennytm, who helped me immeasurably by figuring out that trick!

like image 85
Shepmaster Avatar answered Oct 13 '22 12:10

Shepmaster