Trait 对象强制更高级别的 trait bounds,这打破了嵌套闭包

问题描述 投票:0回答:1

我处于 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);

游乐场:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a5fab1aca580e4fe630f91cfa7ac0639

编辑:Succ 中的“引用的生命周期”。

当我更新该结构以删除引用时(并使用 Rc 来支持克隆):

#[derive(Clone)]
struct Succ<T>(T, Rc<dyn OpTrait>);

嵌套效果很好,尽管

T
需要一个 HRTB on
nest
.

rust closures trait-objects
1个回答
0
投票

考虑以下行:

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
边界。

这里是完整的固定代码:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1fe778d04c5801203111fa5ae558fcee

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.