将发送特征添加到装箱特征对象时的奇怪行为
Strange behavior when adding the Send trait to a boxed trait object
这是一个错误结构:
#[derive(Debug)]
pub struct Error {
msg: &'static str,
//source: Option<Box<dyn std::error::Error>>, // old
source: Option<Box<dyn std::error::Error + Send>>, // new
}
impl Error {
fn new_caused<E>(msg: &'static str, err: E) -> Self
where
E: 'static + std::error::Error + Send,
{
Self {
msg: msg,
source: Some(Box::from(err)),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{}", self.msg) // HACK
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|err| err.as_ref())
}
}
fn main() {
let err = "this will fail".parse::<i32>().unwrap_err();
let err = Error::new_caused("some msg", err);
}
我决定让它 Send
可用,所以我将 source: Option<Box<dyn std::error::Error>>
更改为 source: Option<Box<dyn std::error::Error + Send>>
,但奇怪的事情发生了。
魔法#1
new_caused
拒绝再编译:
error[E0277]: the trait bound `std::boxed::Box<dyn std::error::Error + std::marker::Send>: std::convert::From<E>` is not satisfied
--> src/main.rs:14:26
|
14 | source: Some(Box::from(err)),
| ^^^^^^^^^^^^^^ the trait `std::convert::From<E>` is not implemented for `std::boxed::Box<dyn std::error::Error + std::marker::Send>`
|
= note: required by `std::convert::From::from`
将 Box::from
更改为 Box::new
有所帮助,即使它们的签名看起来相同并且 Box::from
的实现只是调用 Box::new
.
魔法#2
source
也变得不正确:
error[E0308]: mismatched types
--> src/main.rs:27:9
|
27 | self.source.as_ref().map(|err| err.as_ref())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait `std::error::Error`, found trait `std::error::Error + std::marker::Send`
|
= note: expected enum `std::option::Option<&(dyn std::error::Error + 'static)>`
found enum `std::option::Option<&dyn std::error::Error + std::marker::Send>`
为什么未使用的 Send
特征不像其他特征那样被忽略?
用手动版本替换该组合器逻辑效果很好:
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(source) => Some(source.as_ref()),
None => None
}
}
总结
这种“魔法”的解释是什么?有什么更好的处理方法?
对于魔法#1,这是因为标准库有这些实现:
impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>
没有 Sync
就没有 E: Error + Send
的实现。
简单的解决方案 #1:在有 Send
的地方添加 Sync
或使用 Box::new
.
魔法 #2 更复杂:你有一个 std::option::Option<&dyn std::error::Error + Sync>
,而你需要一个 Option<&dyn std::error::Error>
。您知道 &(dyn std::error::Error + Send)
可以转换为 &dyn std::error::Error
,因此您希望 Option<_>
也可以转换为 &dyn std::error::Error
,但这些转换不是可传递的 1,所以它失败了。
map
和match
的区别在于类型推导的顺序:
在 map
的情况下,闭包的类型被推断为采用 Box<dyn std::error::Error + Sync>
。因为它 returns err.as_ref()
,类型 &dyn std::error::Error + Sync
,也就是闭包 returns 的类型。然后 Option::map
returns 一个 Option<_>
具有相同类型的闭包 return 类型所以你得到一个最终的 Option<&dyn std::error::Error + Sync>
和一个错误。
在match
代码中,当你写Some(source) => Some(source.as_ref())
时,source
被推导为类型Box<dyn std::error::Error + Sync>
,但右侧是从[=69=推导]ed 类型 Option<&dyn std::error::Error>
,因此 Some
的参数被转换为该类型:source.as_ref()
被转换为正确的类型并编译。
我认为写这个例子最简单的方法是在映射中添加一个转换as _
,指示编译器从用法而不是从内部代码推断闭包的类型:
self.source.as_ref().map(|err| err.as_ref() as _)
如果代码比较复杂,as _
可能不可行。那么一个match
就完全够用了。
Playground 固定码
1:我想我读过有关自动进行这些转换的内容(covariant on auto traits?)但我在任何地方都找不到...
这是一个错误结构:
#[derive(Debug)]
pub struct Error {
msg: &'static str,
//source: Option<Box<dyn std::error::Error>>, // old
source: Option<Box<dyn std::error::Error + Send>>, // new
}
impl Error {
fn new_caused<E>(msg: &'static str, err: E) -> Self
where
E: 'static + std::error::Error + Send,
{
Self {
msg: msg,
source: Some(Box::from(err)),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{}", self.msg) // HACK
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|err| err.as_ref())
}
}
fn main() {
let err = "this will fail".parse::<i32>().unwrap_err();
let err = Error::new_caused("some msg", err);
}
我决定让它 Send
可用,所以我将 source: Option<Box<dyn std::error::Error>>
更改为 source: Option<Box<dyn std::error::Error + Send>>
,但奇怪的事情发生了。
魔法#1
new_caused
拒绝再编译:
error[E0277]: the trait bound `std::boxed::Box<dyn std::error::Error + std::marker::Send>: std::convert::From<E>` is not satisfied
--> src/main.rs:14:26
|
14 | source: Some(Box::from(err)),
| ^^^^^^^^^^^^^^ the trait `std::convert::From<E>` is not implemented for `std::boxed::Box<dyn std::error::Error + std::marker::Send>`
|
= note: required by `std::convert::From::from`
将 Box::from
更改为 Box::new
有所帮助,即使它们的签名看起来相同并且 Box::from
的实现只是调用 Box::new
.
魔法#2
source
也变得不正确:
error[E0308]: mismatched types
--> src/main.rs:27:9
|
27 | self.source.as_ref().map(|err| err.as_ref())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait `std::error::Error`, found trait `std::error::Error + std::marker::Send`
|
= note: expected enum `std::option::Option<&(dyn std::error::Error + 'static)>`
found enum `std::option::Option<&dyn std::error::Error + std::marker::Send>`
为什么未使用的 Send
特征不像其他特征那样被忽略?
用手动版本替换该组合器逻辑效果很好:
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(source) => Some(source.as_ref()),
None => None
}
}
总结
这种“魔法”的解释是什么?有什么更好的处理方法?
对于魔法#1,这是因为标准库有这些实现:
impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>
没有 Sync
就没有 E: Error + Send
的实现。
简单的解决方案 #1:在有 Send
的地方添加 Sync
或使用 Box::new
.
魔法 #2 更复杂:你有一个 std::option::Option<&dyn std::error::Error + Sync>
,而你需要一个 Option<&dyn std::error::Error>
。您知道 &(dyn std::error::Error + Send)
可以转换为 &dyn std::error::Error
,因此您希望 Option<_>
也可以转换为 &dyn std::error::Error
,但这些转换不是可传递的 1,所以它失败了。
map
和match
的区别在于类型推导的顺序:
在 map
的情况下,闭包的类型被推断为采用 Box<dyn std::error::Error + Sync>
。因为它 returns err.as_ref()
,类型 &dyn std::error::Error + Sync
,也就是闭包 returns 的类型。然后 Option::map
returns 一个 Option<_>
具有相同类型的闭包 return 类型所以你得到一个最终的 Option<&dyn std::error::Error + Sync>
和一个错误。
在match
代码中,当你写Some(source) => Some(source.as_ref())
时,source
被推导为类型Box<dyn std::error::Error + Sync>
,但右侧是从[=69=推导]ed 类型 Option<&dyn std::error::Error>
,因此 Some
的参数被转换为该类型:source.as_ref()
被转换为正确的类型并编译。
我认为写这个例子最简单的方法是在映射中添加一个转换as _
,指示编译器从用法而不是从内部代码推断闭包的类型:
self.source.as_ref().map(|err| err.as_ref() as _)
如果代码比较复杂,as _
可能不可行。那么一个match
就完全够用了。
Playground 固定码
1:我想我读过有关自动进行这些转换的内容(covariant on auto traits?)但我在任何地方都找不到...