1

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

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也是A。我试过了impl<'a> A for &'a A,但这也无济于事。有没有什么办法解决这一问题?

4

2 回答 2

4

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

有了这个要求, aBox将不起作用。ABox 拥有它的数据,但有时您借用了无法移动的数据。

标准库中有一种称为 的类型Cow,它是对值是借用还是拥有的抽象。但是,它在这里可能不太适合您,因为它不会让您拥有数据作为 aBox并且它还要求您的数据类型必须实现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()))
    }
}
于 2018-09-16T11:31:23.897 回答
3

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

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

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

但这有点傻。Box在这里完全不需要引用,并且将其强制转换为Box<dyn A>只是为调用者增加了另一层间接性。正如彼得霍尔的回答所描述的那样最好返回一个封装“盒装特征对象对特征对象的引用”的想法的类型。


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

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

使用这个 trait 定义,实现的每个类型都ProducerOrContainer可以选择它返回的类型,因此您可以选择Box<dyn A>一些impls 和&'a dyn A其他类型。然而,这在当前的 Rust (1.29) 中是不可能的。

于 2018-09-16T13:04:31.573 回答