尝试对特征对象装箱时出现“预期特征A,找到并出现A”

时间:2018-09-16 10:16:28

标签: rust trait-objects

我正在尝试创建一个特征,该特征可以检索(返回对另一个特征的一个特征对象),也可以创建一个特征(并返回其盒装版本),然后将选择权留给实现者(意味着我需要将返回对象的生存期限制为生产者的生存期。但是,我遇到了错误:

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,但这也无济于事。有什么办法可以解决这个问题?

2 个答案:

答案 0 :(得分:4)

  

...可以检索另一个特征的特征对象(并返回对其的引用),也可以创建一个特征对象(并返回其盒装版本)。

根据此要求,Box将不起作用。 Box 拥有数据,但有时您借来的数据无法移动。

标准库中有一个名为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()))
    }
}

答案 1 :(得分:2)

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

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

闭包不是coercion site。编译器查看闭包的内容以查看返回值是什么,并得出结论,它返回Box<&'a dyn A>。但是闭包本身不能从“函数返回Box<&'a dyn A>”强制为“函数返回Box<dyn A + 'a>”,因为这些类型在结构上是不同的。您添加了强制类型转换,以告知编译器您希望闭包首先返回Box<dyn A>

但这有点愚蠢。这里Box完全不需要引用,而将其强制转换为Box<dyn A>只会为调用者增加另一种间接访问级别。最好返回一个封装“ {em>或者装箱的特征对象,对特征对象的引用”这样的类型,如Peter Hall's answer所述


在Rust的未来版本中,具有通用关联类型(“ GAT”),可以将返回类型设置为ProducerOrContainer的关联类型,如下所示:

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

使用此特征定义,实现ProducerOrContainer的每种类型都可以选择返回的类型,因此您可以为某些Box<dyn A>选择impl,为其他&'a dyn A选择。但是,这在当前的Rust(1.29)中是不可能的。