添加Send trait到boxed trait object时出现奇怪的行为

3

这里是一个错误结构体:

#[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个回答

5

对于魔法#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没有Sync的实现。

简单的解决方法 #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<_>也是可以转换的,但是这些转换不是传递性的1,所以它失败了。

mapmatch之间的区别在于类型推导的顺序:

map的情况下,闭包的类型被推断为Box<dyn std::error::Error + Sync>。由于它返回err.as_ref(),类型为&dyn std::error::Error + Sync,这就是闭包返回的类型。然后,Option::map返回一个具有相同类型的闭包返回类型的Option<_>,因此你得到一个最终的Option<&dyn std::error::Error + Sync>和错误。

match代码中,当你编写Some(source) => Some(source.as_ref())时,source被推断为类型Box<dyn std::error::Error + Sync>,但是右侧从返回类型Option<&dyn std::error::Error>中推断出来,因此Some的参数被转换为那个类型:source.as_ref()被转换为正确的类型,它可以编译通过。

我认为编写这个例子最简单的方法是在map中添加as _转换,以指示编译器从使用中推断闭包的类型,而不是从内部代码中推断:

self.source.as_ref().map(|err| err.as_ref() as _)

如果代码更为复杂,as _ 可能就不可行了。这时可以使用match语句来完成。 Playground 上有修复后的代码。
我记得我曾经阅读过关于自动进行这些转换的(针对自动特性的协变?),但是我在任何地方都找不到它...

我总是需要花费很长时间来思考它们,通常还会搞错,所以我在这方面没有什么帮助! - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接