rust - 使用具有泛型的更高级别的特征边界
问题描述
我偶然发现了一个有趣的边缘案例:使用排名更高的生命周期边界来接受返回泛型参数的闭包,例如for<'a> FnOnce(&'a T) -> R: MyTrait
. 没有办法指定R
生命最多'a
。也许最好用一个例子来解释。
让我们定义一个简单的类引用类型包装一个值:
struct Source;
struct Ref<'a> {
source: &'a Source,
value: i32,
}
为方便起见,让我们添加一个辅助构造函数。在这里,我将使用显式生命周期来使借用不言自明:
impl Source {
fn new_ref<'a>(&'a self, value: i32) -> Ref<'a> {
Ref { source: self, value }
}
}
这是一个非常奇特的整数复制例程实现,使用 HRTB 并在我们的Ref
:
fn call_1<F>(callback: F) -> i32
where
for<'a> F: FnOnce(&'a Source) -> Ref<'a>,
{
let source = Source;
callback(&source).value
}
fn fancy_copy_1(value: i32) -> i32 {
call_1(|s| s.new_ref(value))
}
这很好,并且按预期工作。我们知道它Ref
不会超过Source
并且编译器也能够接受它。现在让我们创建一个简单的 trait 并实现它以供我们参考:
trait MyTrait {
fn value(&self) -> i32;
}
impl<'a> MyTrait for Ref<'a> {
fn value(&self) -> i32 {
self.value
}
}
并修改我们的整数复制例程以返回实现该特征的泛型类型,而不仅仅是返回Ref
:
fn call_2<R, F>(callback: F) -> i32
where
for<'a> F: FnOnce(&'a Source) -> R,
R: MyTrait,
{
let source = Source;
callback(&source).value()
}
fn fancy_copy_2(value: i32) -> i32 {
call_2(|s| s.new_ref(value))
}
突然我得到一个错误:cannot infer an appropriate lifetime for autoref due to conflicting requirements
. 为了方便起见,Rust 操场链接。从某些角度来看,这实际上是有道理的:与Ref<'a>
第一个示例不同,我从未说过R
最多只能活到'a
. 很可能活得更久,因此可以访问释放的内存。所以我需要用它自己的生命周期来注释它。但是没有地方可以做!第一个本能是把生命限制在界限内:
where
for<'a> F: FnOnce(&'a Source) -> R,
R: MyTrait + 'a,
这当然是不正确的,因为'a
只为第一个界限定义。
这就是我感到困惑的地方,开始搜索,但从未发现任何关于将 HRTB 和泛型类型结合在一起的信息。也许在 Rust 中更有经验的人有什么建议?
更新 1。
当我更多地考虑这个问题时,我记得我可以使用impl Trait
语法。这看起来像我的问题的解决方案:
fn call_3<F>(callback: F) -> i32
where
F: for<'a> FnOnce(&'a Source) -> (impl MyTrait + 'a),
{
let source = Source;
callback(&source).value()
}
fn fancy_copy_3(value: i32) -> i32 {
call_3(|s| Box::new(s.new_ref(value)))
}
然而,这不起作用,因为由于impl MyTrait
某种原因(可能是暂时的)不允许在这个地方。但这让我想到了dyn Trait
语法,而且确实有效!
fn call_4<F>(callback: F) -> i32
where
F: for<'a> FnOnce(&'a Source) -> Box<dyn MyTrait + 'a>,
{
let source = Source;
let value = callback(&source); // note how a temporary is required
value.value()
}
fn fancy_copy_4(value: i32) -> i32 {
call_4(|s| Box::new(s.new_ref(value)))
}
这是我的解决方案:使用 dyn Trait
语法有一个地方可以放置+ 'a
! 不幸的是,这个解决方案对我来说仍然不太适用,因为它需要 trait 上的对象安全性,并且增加了分配装箱值的开销。但至少它是一些东西。