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:
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!