将发送特征添加到装箱的特征对象时出现奇怪的行为

时间:2020-09-09 11:44:54

标签: multithreading rust compiler-errors thread-safety 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 个答案:

答案 0 :(得分:4)

对于魔术#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>

没有E: Error + Send的{​​{1}}无法实现。

简单的解决方案#1:在有Sync的地方添加Sync或使用Send

魔术#2更为复杂:您有一个Box::new,需要一个std::option::Option<&dyn std::error::Error + Sync>。您知道Option<&dyn std::error::Error>可以转换为&(dyn std::error::Error + Send),因此您希望&dyn std::error::Error也可以转换,但是这些转换不是可传递的 1 ,因此失败

Option<_>map之间的区别在于类型推导的顺序:

match情况下,闭包的类型推导为map。由于它返回类型为Box<dyn std::error::Error + Sync>的{​​{1}},因此闭包将返回该类型。然后err.as_ref()返回一个&dyn std::error::Error + Sync,其类型与闭包返回类型相同,因此您将得到最终的Option::map和一个错误。

Option<_>代码中,当您编写Option<&dyn std::error::Error + Sync>时,match被推导为类型Some(source) => Some(source.as_ref()),但是从返回的类型{{1}推导出右侧。 },因此将source的参数强制转换为该类型:Box<dyn std::error::Error + Sync>被转换为正确的类型并进行编译。

我认为编写此示例的最简单方法是在映射内部添加强制转换Option<&dyn std::error::Error>,以指示编译器从用法而不是从内部代码推导闭包的类型:

Some

如果代码更复杂,则source.as_ref()可能不可行。那么as _就足够了。

Playground和固定代码。


1:我想我读过有关自动进行这些转换的信息(关于自动特征,它们是 covariant ?),但我在任何地方都找不到它...

相关问题