我正在尝试创建一个特征,该特征可以检索(返回对另一个特征的一个特征对象),也可以创建一个特征(并返回其盒装版本),然后将选择权留给实现者(意味着我需要将返回对象的生存期限制为生产者的生存期。但是,我遇到了错误:
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
,但这也无济于事。有什么办法可以解决这个问题?
答案 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)中是不可能的。