"Expected trait A, found &A" 尝试装箱特征对象时

"Expected trait A, found &A" when trying to box a trait object

我正在尝试制作一个可以检索(和 return 引用)另一个特征的特征对象,或者创建一个(和 return 它的盒装版本),将选择留给实现者(这意味着我需要将 returned 对象的生命周期限制为生产者的生命周期)。但是,我 运行 出错了:

use std::borrow::Borrow;
use std::collections::HashMap;

trait A { 
    fn foobar(&self) {
        println!("!"); 
    } 
}

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>>;
}

impl<'b, B: Borrow<A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>> {
        self.get(name).map(|borrow| Box::new(borrow.borrow()))
    }
}

错误是:

error[E0308]: mismatched types
  --> src/main.rs:20:9
   |
20 |         self.get(name).map(|borrow| Box::new(borrow.borrow()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait A, found &A
   |
   = note: expected type `std::option::Option<std::boxed::Box<dyn A + 'a>>`
              found type `std::option::Option<std::boxed::Box<&dyn A>>`

这让我很困惑,因为我希望 &A 也是 A。我试过 impl<'a> A for &'a A,但这也无济于事。有什么办法可以解决这个问题吗?

...that can either retrieve (and return a reference to) a trait object of another trait, or create one (and return a boxed version of it).

根据此要求,Box 将不起作用。 ABox拥有它的数据,但有时你有借来的数据,你不能移动。

标准库中有一种叫做Cow的类型,它是对一个值是被借用还是被拥有的抽象。但是,它在这里可能不太适合你,因为它不会让你拥有数据作为 Box 并且它还要求你的数据类型必须实现 ToOwned.

但我们可以将您的要求直接建模为 enum:

enum BoxOrBorrow<'a, T: 'a + ?Sized> {
    Boxed(Box<T>),
    Borrowed(&'a T),
}

并通过实施 Deref:

使其符合人体工程学
use std::ops::Deref;

impl<'a, T> Deref for BoxOrBorrow<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        match self {
            BoxOrBorrow::Boxed(b) => &b,
            BoxOrBorrow::Borrowed(b) => &b,
        }
    }
}

这使您可以将自定义 BoxOrBorrow 类型视为任何其他引用 - 您可以使用 * 取消引用它或将其传递给任何需要引用 T 的函数。

这就是您的代码的样子:

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>;
}

impl<'b, B: Borrow<dyn A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>> {
        self.get(name)
            .map(|b| BoxOrBorrow::Borrowed(b.borrow()))
    }
}

您可以通过为 &'_ dyn A 实现 A 并添加显式强制转换来编译原始代码:

self.get(name).map(|borrow| Box::new(borrow.borrow()) as Box<dyn A>)

闭包不是 coercion site。编译器查看闭包的内容以查看 return 值是什么,并得出它 returns Box<&'a dyn A> 的结论。但是闭包本身不能从 "function returning Box<&'a dyn A>" 强制转换为 "function returning Box<dyn A + 'a>",因为这些类型在结构上是不同的。您添加强制转换是为了告诉编译器您首先要将闭包 return Box<dyn A>

但这有点傻。 Box 引用在这里完全没有必要,将其转换为 Box<dyn A> 只是为调用者增加了另一个间接级别。 return 一种封装了“或者 盒装特征对象, 对特征的引用的想法的类型会更好对象”,如 所述。


在 Rust 的未来版本中,使用通用关联类型 ("GATs"),可以使 return 类型成为 ProducerOrContainer 的关联类型,类似于以下:

trait ProducerOrContainer {
    type Result<'a>: A;
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Result<'a>>;
}

有了这个特征定义,每个实现 ProducerOrContainer 的类型都可以选择它 return 的类型,所以你可以为一些 impl 选择 Box<dyn A>&'a dyn A 给其他人。但是,这在当前的 Rust (1.29) 中是不可能的。