我处于 Rust 让我为通用类型添加 HRTB 的情况,该通用类型用作特征对象中的参数。但是这个 HRTB 使得嵌套闭包不起作用。
这是我要用来制作特征对象的特征
Box<dyn OpTrait>
:
trait OpTrait {}
struct Op<T>(T);
impl<T> OpTrait for Op<T> {}
这是具有特征对象的结构:
#[derive(Clone)]
struct Succ<'a, T>(T, &'a RefCell<Option<Box<dyn OpTrait>>>);
impl<'a, T: Clone> Succ<'a, T>
where
for<'c> T: 'c,
{
fn trace(&self) -> Self {
let b = Box::new(Op(self.0.clone()));
self.1.borrow_mut().insert(b);
Succ(self.0.clone(), self.1)
}
}
#[derive(Debug, Clone)]
struct Zero;
这是将它们放在一起的功能:
fn nest<T: Clone, F>(f: F, t: &T) -> T
where
for<'a> F: Fn(&Succ<'a, T>) -> Succ<'a, T>,
{
let trace = RefCell::new(None);
let nested = Succ(t.clone(), &trace);
let result = f(&nested);
result.0
}
我可以这样使用:
let input = Zero;
let result0 = nest(|n| n.trace(), &input);
有效。
但实际上嵌套
nest
调用停止工作:
let result = nest(|n| nest(|nn| nn.trace(), n), &input);
--> src/main.rs:46:37
|
46 | let result = nest(|n| nest(|nn| nn.trace(), n), &input);
| - ^^^^^^^^^^
| | |
| | `n` escapes the closure body here
| | argument requires that `'1` must outlive `'static`
| `n` is a reference that is only valid in the closure body
| has type `&Succ<'1, Zero>`
注:
Rust 让我在
for<'c> T: 'c
中添加 impl
以获得 Succ
- 与 trait 对象有关,我不太确定。这个 HRTB 会导致嵌套闭包的问题——如果没有它,嵌套闭包可以正常工作:
// ok
let result2 = nest(|n| nest(|nn| nn.clone(), n), &input);
编辑:Succ 中的“引用的生命周期”。
当我更新该结构以删除引用时(并使用 Rc 来支持克隆):
#[derive(Clone)]
struct Succ<T>(T, Rc<dyn OpTrait>);
嵌套效果很好,尽管
T
需要一个 HRTB on nest
.
考虑以下行:
let result1 = nest(|n| nest(|nn| nn.trace(), n), &input);
n
由于 &Succ<'_, Zero>
的定义而具有 nest
类型,并且出于同样的原因 nn
具有 &Succ<'_, &Succ<'_, Zero>>
类型(所有隐藏的生命周期都不同,因为您没有使用相同的参数指定生命周期Fn
边界的参数。不过这并不重要)。
然后您尝试在
.trace()
上调用 nn
,这需要绑定 for<'c> T: 'c
。这个界限与T: 'static
本质上是一样的,为了看到这个你可以只取'c = 'static
,它必须是有效的,因为你的界限在每一个可能的生命周期'c
中都是必需的。在这个调用中,来自 T
定义的 trace
实际上是 &Succ<'_, Zero>
,但是它包含未知的生命周期,因此不是 'static
并且边界 for<'c> T: 'c
不满足。
result0
起作用的原因是因为你有一个&Succ<'_, Zero>
,其中T
中的.trace()
是Zero
,也就是'static
,所以一切都很好。
与此同时,在
result2
中,您没有调用 .trace()
,这就是之前需要 'static
绑定的原因,因此这也有效。
您的代码中的根本问题是
Box<dyn OpTrait>
隐含为 Box<dyn OpTrait + 'static>
,而您实际上想要一个 Box<dyn OpTrait + 'lifetime_of_t>
。很自然地引入一生't
在Box
中使用并绑定T
。即,将Succ
的定义改为:
#[derive(Clone)]
struct Succ<'a, 't, T: 't>(T, &'a RefCell<Option<Box<dyn OpTrait + 't>>>);
然后更新其他方法以在需要时添加
't
生命周期和T: 't
边界。
use std::{
cell::RefCell,
fmt::Debug,
};
trait OpTrait {}
struct Op<T>(T);
impl<T> OpTrait for Op<T> {}
#[derive(Clone)]
struct Succ<'a, 't, T: 't>(T, &'a RefCell<Option<Box<dyn OpTrait + 't>>>);
impl<'a, 't, T: Clone + 't> Succ<'a, 't, T>
{
fn trace(&self) -> Self {
let b = Box::new(Op(self.0.clone()));
self.1.borrow_mut().insert(b);
Succ(self.0.clone(), self.1)
}
}
#[derive(Debug, Clone)]
struct Zero;
fn nest<'t, T: Clone + 't, F>(f: F, t: &T) -> T
where
for<'a> F: Fn(&Succ<'a, 't, T>) -> Succ<'a, 't, T>,
{
let trace = RefCell::new(None);
let nested = Succ(t.clone(), &trace);
let result = f(&nested);
result.0
}
fn main() {
let input = Zero;
// ok
let result0 = nest(|n| n.trace(), &input);
dbg!(result0);
// BOOOM
let result1 = nest(|n| nest(|nn| nn.trace(), n), &input);
dbg!(result1);
// ok
let result2 = nest(|n| nest(|nn| nn.clone(), n), &input);
dbg!(result2);
}