为什么 Box<dyn Error> 不实现 Error?

lap*_*tou 6 rust

为什么不Box<dyn Error>执行Error

我试图使用context()从方法anyhowResult<surf::Response, surf::Error>,它不是合作:

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")?;
Run Code Online (Sandbox Code Playgroud)

即使有这个丑陋的演员,它仍然不起作用,因为surf::Error没有实现Error,也没有实现Box<dyn Error>

She*_*ter 9

确实没有;当我最初了解到它时,这让包括我在内的人们感到非常惊讶:

fn impls_error<T: std::error::Error>() {}
impls_error::<Box<dyn std::error::Error>>();
Run Code Online (Sandbox Code Playgroud)
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>`
Run Code Online (Sandbox Code Playgroud)

跟踪此问题的 Rust 问题 (#60759) 中列出了简短的基本原理:

不幸的是,扩展impl<T: Error> Error for Box<T>to 也申请T: ?Sized失败,因为然后impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>开始与impl<T> From<T> for T.

长期的理由是 Rust有这个全面的实现

impl<T: Error> Error for Box<T> {}
Run Code Online (Sandbox Code Playgroud)

这个泛型类型有一个隐式Sized边界,所以它可以等价地写成:

impl<T: Error + Sized> Error for Box<T> {}
Run Code Online (Sandbox Code Playgroud)

如果您尝试将此实现应用于Box<dyn Error>,则T需要等于dyn Error。编译器不允许这样做,因为dyn Error它本身是unsized

解决此问题的下一步是放宽允许?Sized类型的限制:

impl<T: Error + ?Sized> Error for Box<T> {}
Run Code Online (Sandbox Code Playgroud)

但是,如果你这样做,那么你会发生冲突的实现最终From特质的毯子实现一个具体错误的转换Box<dyn Error>

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

impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> {} // #2
Run Code Online (Sandbox Code Playgroud)

如果我们可以同时使用这两个实现,那么这段代码应该发生什么就会变得模棱两可:

let a: Box<dyn Error> = todo!();
let b: Box<dyn Error> = a.into();
Run Code Online (Sandbox Code Playgroud)

它应该保持原样(遵循 impl 1)还是再次装箱(遵循 impl 2)?大多数人可能想要1,但两条路径都是有效的。这是关于为什么此时Box<dyn Error>无法实现Error自身的长篇回答。

将来,专业化可能允许这两者重叠并选择特定案例(我猜可能是1)。

也可以看看:


无论如何,我的竞争对手 SNAFU 也面临着同样的问题。在那里,我们引入了一个特性来帮助解决这个问题。最容易查看Snafu宏扩展到的内容(稍微清理一下):

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

变成

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

这样,我们不需要依赖于source实际实现Error本身,而是有一些不与一揽子实现重叠的具体实现:

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
Run Code Online (Sandbox Code Playgroud)

这个解决方案 95% 的功劳归功于kennytm,他通过找出这个技巧为我提供了不可估量的帮助!