我偶然发现了一个有趣的边缘情况:使用排名较高的生命周期边界来接受返回通用参数的闭包,例如for<'a> FnOnce(&'a T) -> R: MyTrait. 没有办法指定R最多能活多久'a. 也许最好用一个例子来解释。
让我们定义一个简单的类似引用的类型来包装一个值:
struct Source;
struct Ref<'a> {
source: &'a Source,
value: i32,
}
Run Code Online (Sandbox Code Playgroud)
为方便起见,让我们添加一个辅助构造函数。在这里,我将使用显式生命周期来使借用不言自明:
impl Source {
fn new_ref<'a>(&'a self, value: i32) -> Ref<'a> {
Ref { source: self, value }
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个非常奇特的整数复制例程的实现,它使用 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))
}
Run Code Online (Sandbox Code Playgroud)
这很好,并且按预期工作。我们知道它的Ref寿命不会超过Source并且编译器也能够接收它。现在让我们创建一个简单的 trait 并实现它以供我们参考:
trait MyTrait {
fn value(&self) -> i32;
}
impl<'a> MyTrait for Ref<'a> {
fn value(&self) -> i32 {
self.value
}
}
Run Code Online (Sandbox Code Playgroud)
并修改我们的整数复制例程以返回实现该特征的泛型类型,而不仅仅是返回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))
}
Run Code Online (Sandbox Code Playgroud)
突然我收到一个错误: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,
Run Code Online (Sandbox Code Playgroud)
这当然是不正确的,因为'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)))
}
Run Code Online (Sandbox Code Playgroud)
但是,这不起作用,因为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)))
}
Run Code Online (Sandbox Code Playgroud)
这是我的解决方案:使用 dyn Trait语法,可以放置+ 'a! 不幸的是,这个解决方案对我来说仍然不太好用,因为它需要特性上的对象安全性,并且增加了分配盒装值的开销。但至少它是一些东西。