首页 > 解决方案 > 导致类型不匹配的特征对象

问题描述

从外部库实现特征时,我发现我的初始实现无法编译。

// From library
trait SomeTrait {
    fn do_thing(&self) -> Result<i32, Box<dyn Error + Sync + Send + 'static>>;
}

// My code
struct Foo;

impl SomeTrait for Foo {
    fn do_thing(&self) -> Result<i32, Box<dyn Error + Sync + Send + 'static>> {
        i32::from_str("42").map_err(|e| Box::new(MyError))
    }
}

#[derive(Debug)]
struct MyError;
impl Display for MyError {...}
impl Error for MyError {}

来自 rustc v1.55.0 的错误:

error[E0308]: mismatched types
  --> src/main.rs:17:9
   |
16 |     fn do_thing(&self) -> Result<i32, Box<dyn Error + Sync + Send + 'static>> {
   |                           --------------------------------------------------- expected `Result<i32, Box<(dyn std::error::Error + Send + Sync + 'static)>>` because of return type
17 |         i32::from_str("42").map_err(|e| Box::new(MyError))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn std::error::Error`, found struct `MyError`
   |
   = note: expected enum `Result<_, Box<(dyn std::error::Error + Send + Sync + 'static)>>`
              found enum `Result<_, Box<MyError>>`

如果我将主体更改do_thing为这个,编译器会很高兴。

match i32::from_str("42") {
    Ok(v) => Ok(v),
    Err(_) => Err(Box::new(MyError)),
}

我也可以这样做,编译器也接受但非常难看。

i32::from_str("42").map_err(|e| Box::new(MyError) as Box<dyn Error + Sync + Send + 'static>)

我显然有一个可行的解决方案,但我想知道实现之间的区别是什么,以避免将来拉我的头发。

标签: rust

解决方案


Box<MyError>to的转换Box<dyn Error + ...>称为unsizing coercion(因为它从具有静态已知大小的类型转换为没有大小的类型)。可以如此转换类型的确切规则由特殊特征CoerceUnsized和定义Unsize。(这些特性目前是不稳定的,但这只是意味着你不能在稳定的 Rust 中实现或直接调用它们;你仍然可以利用它们的效果。)

文档告诉我们存在以下实现:

impl<T, U, A> CoerceUnsized<Box<U, A>> for Box<T, A>
where
    T: Unsize<U> + ?Sized,
    A: Allocator,
    U: ?Sized, 

这个通用实现涵盖了以下情况

impl CoerceUnsized<Box<dyn Error + ...>> for Box<MyError>

这样您就可以在内部强制map_err执行,但是对于 没有这样的实现Result,因此您不能对整个执行强制执行Result,并且必须通过将错误值带出到它自己的类型变量Box<MyError>分配或返回到需要Box<dyn Error + ...>.


这就是您要的“原因”。现在是编写代码的提示:您可以as _说“我想要一个强制,但请推断类型,因为它很明显”:

    i32::from_str("42").map_err(|e| Box::new(MyError) as _)

这样您就不必编写冗长的dyn类型。您还可以定义一个类型别名来减少重复,同时仍然是明确的:

type DynError = Box<dyn Error + Sync + Send + 'static>;

...

    i32::from_str("42").map_err(|e| Box::new(MyError) as DynError)

但是,对于 的特定情况Box<dyn Error>,更好的选择是仅使用?运算符及其自动Into转换:

    fn do_thing(&self) -> Result<i32, Box<dyn Error + Sync + Send + 'static>> {
        Ok(i32::from_str("42")?)
    }

这是有效?的,因为通过转换返回错误Into::into(e),并且标准库包含从any Error到的通用转换Box<dyn Error + Send + Sync>

impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>

这可能是图书馆希望您使用的。

在这个简单的例子中, the?也等价于:

    fn do_thing(&self) -> Result<i32, Box<dyn Error + Sync + Send + 'static>> {
        i32::from_str("42").map_err(Into::into)
    }

推荐阅读