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

为什么不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")?;

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

回答

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

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>`

跟踪此问题的 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> {}

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

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

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

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

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

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

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

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

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

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

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

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

也可以看看:

  • 使用泛型类型时,“From”的实现如何存在冲突?

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

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

变成

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

这样,我们不需要依赖于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

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


以上是为什么Box&lt;dynError&gt;不实现Error?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>