为什么不Box<dyn Error>
执行Error
?
我试图使用context()
从方法anyhow
上Result<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>
。
确实没有;当我最初了解到它时,这让包括我在内的人们感到非常惊讶:
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,他通过找出这个技巧为我提供了不可估量的帮助!
归档时间: |
|
查看次数: |
485 次 |
最近记录: |