rust Trait对象强制更高级别的trait边界,这会破坏嵌套的闭包

jxct1oxe  于 2023-04-06  发布在  其他
关注(0)|答案(1)|浏览(118)

我遇到的情况是,Rust让我为一个泛型类型添加一个HRTB,该泛型类型在trait对象中用作参数。但是这个HRTB使嵌套闭包无法工作。
下面是我将用来创建trait对象Box<dyn OpTrait>的trait:

trait OpTrait {}

struct Op<T>(T);

impl<T> OpTrait for Op<T> {}

下面是带有trait对象的struct:

#[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让我在Succimpl中添加for<'c> T: 'c-这与trait对象有关,我不太确定。这个HRTB导致了嵌套闭包的问题-没有它,嵌套闭包工作正常:

// ok
let result2 = nest(|n| nest(|nn| nn.clone(), n), &input);

Playground:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a5fab1aca580e4fe630f91cfa7ac0639
编辑:有一些与'一辈子的参考成功。
当我更新该结构体以删除引用(并使用Rc来支持Clone)时:

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

尽管nest上的T需要多一个HRTB,但这对于嵌套来说工作得很好。

niknxzdl

niknxzdl1#

请考虑以下行:

let result1 = nest(|n| nest(|nn| nn.trace(), n), &input);

由于nest的定义,n的类型为&Succ<'_, Zero>,出于同样的原因,nn的类型为&Succ<'_, &Succ<'_, Zero>>(所有隐藏的生命周期都是不同的,因为您没有在Fn绑定的参数中指定具有相同参数的生命周期。尽管这并不重要)。
然后,您尝试在nn上调用.trace(),这需要绑定for<'c> T: 'c。这个绑定本质上与T: 'static相同,要看到这一点,您可以使用'c = 'static,它必须是有效的,因为每个可能的生存期'c都需要您的边界。在这个调用中,来自trace定义的T实际上是&Succ<'_, Zero>,然而它包含未知的生命期,因此不是'static,并且不满足for<'c> T: 'c的边界。
result0工作的原因是因为你有一个&Succ<'_, Zero>,其中.trace()中的TZero,也就是'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);
}

相关问题