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>
.
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 forT: ?Sized
fails because then theimpl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>
starts overlapping withimpl<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:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With