rust 如何将闭包的泛型参数的生命周期与闭包本身的生命周期解耦?

yfjy0ee7  于 10个月前  发布在  其他
关注(0)|答案(2)|浏览(156)

我正在做一个rust项目,我希望存储一个Vec的装箱闭包,并带有一个泛型参数,以便以后重用。我想修改这些存储的闭包中的每一个,以添加一些公共处理,但我希望能够使用的几个版本的代码有问题,需要在闭包的泛型参数上绑定一些'static,我认为实际上并不需要,因为T的寿命应该独立于闭包的寿命。
所有给出的示例都可以在rust playground中运行,轻松地注解和取消注解相关的测试函数。此外,为了清晰起见,所有示例都使用了以下类型别名:

pub type MyOp<T> = dyn Fn(&mut String, &mut T);

字符串
下面的例子显示了有问题的代码:

fn test<T>() -> Vec<Box<MyOp<T>>> {
    let mut operators: Vec<Box<MyOp<T>>> = Vec::default();

    let mut add_op = |operator: Box<MyOp<T>>| {
        let operator_bis = move |s: &mut _, t: &mut _| {
            operator(s, t);
            // Do something else with s
        };
        operators.push(Box::new(operator_bis));
    };

    add_op(Box::new(|s, t| ()));
    add_op(Box::new(|s, t| ()));

    operators
}


这段代码编译失败,出现了以下错误,这是我没有预料到的,因为T的生命周期不应该影响最终装箱闭包的生命周期。

error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:14:24
   |
14 |         operators.push(Box::new(operator_bis));
   |                        ^^^^^^^^^^^^^^^^^^^^^^
   |                        |
   |                        the parameter type `T` must be valid for the static lifetime...
   |                        ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
6  | fn test<T: 'static>() -> Vec<Box<MyOp<T>>> {
   |          +++++++++


有趣的是,不调用操作符闭包允许它编译,但显然代码没有达到我的最初目标。

fn test<T>() -> Vec<Box<MyOp<T>>> {
    let mut operators: Vec<Box<MyOp<T>>> = Vec::default();

    let mut add_op = |operator: Box<MyOp<T>>| {
        operators.push(operator);
    };

    add_op(Box::new(|_s, _rng| ()));
    add_op(Box::new(|_s, _rng| ()));

    operators
}


下面的例子展示了一个依赖于helper函数来修改闭包的工作版本。这个版本的优点是可以接受装箱和未装箱的闭包。

fn add_operator<T>(
    operator: impl Fn(&mut String, &mut T) + 'static,
    operators: &mut Vec<Box<MyOp<T>>>,
) {
    let op = move |s: &mut _, t: &mut _| {
        operator(s, t);
        // Do something else with s
    };
    operators.push(Box::new(op));
}

fn test<T>() -> Vec<Box<MyOp<T>>> {
    let mut operators = Vec::default();
    add_operator(Box::new(|s: &mut _, t: &mut _| ()), &mut operators);
    add_operator(|s, t| (), &mut operators);
    operators
}


然而,我也希望下面的版本只接受盒装闭包来编译:

fn add_operator<T>(
    operator: Box<MyOp<T>>,
    operators: &mut Vec<Box<MyOp<T>>>,
) {
    let op = move |s: &mut _ , t: &mut _| {
        operator(s, t);
        // Do something else with s
    };
    operators.push(Box::new(op));
}

fn test<T>() -> Vec<Box<MyOp<T>>> {
    let mut operators = Vec::default();
    add_operator(Box::new(|s: &mut _, t: &mut _| ()), &mut operators);
    add_operator(Box::new(|s: &mut _, t: &mut _| ()), &mut operators);
    operators
}


我认为这可能与rust存储库上的this issue有关,因为编译器无法检测到T的生存期并不真正相关。然而,我对我对与闭包相关的生存期问题的理解并不那么有信心,在打开一个问题之前,我想知道是否有人可以指出我的代码中的错误,或者是否所有的版本都应该能够编译。

  • 在下面编辑后添加信息。*

由于到目前为止收到的评论和答案都解释了如何使示例编译,考虑到dyn类型别名上隐藏的'static绑定,我试图提出一个很好的示例来表明我希望T的生存期独立于函数的生存期。
我们的目标是能够像下面的例子中那样做,其中函数应该能够用任何Cow调用,但实际上失败了,即使使用我最初认为可以工作的版本。在这种情况下,a的生存期即使它不应该发挥作用,因为它在调用操作符后没有使用。

fn main() {
    let operators_cow = test::<Cow<'_, [u32]>>();

    let mut s = String::new();
    let a = [10u32, 20];
    let mut t = Cow::from(a.as_slice());
    operators_cow[0](&mut s, &mut t);

    drop(operators_cow); // Does not compile if uncommented 
}

qv7cva1a

qv7cva1a1#

正如cafce25所指出的,pub type MyOp<T> = dyn Fn(&mut String, &mut T);等价于pub type MyOp<T> = dyn Fn(&mut String, &mut T) + 'static;。你需要为类型别名添加一个生存期,以及使用它的函数。
如果你正确地用生存期注解代码,你的代码就能编译:

pub type MyOp<'a, T> = dyn Fn(&mut String, &mut T) + 'a;

fn add_operator<'a, T: 'a>(
    operator: Box<MyOp<'a, T>>,
    operators: &mut Vec<Box<MyOp<'a, T>>>,
) {
    let op = move |s: &mut _ , t: &mut _| {
        operator(s, t);
        // Do something else with s
    };
    operators.push(Box::new(op));
}

fn test<'a, T: 'a>() -> Vec<Box<MyOp<'a, T>>> {
    let mut operators = Vec::default();
    add_operator(Box::new(|s: &mut _, t: &mut _| ()), &mut operators);
    add_operator(Box::new(|s: &mut _, t: &mut _| ()), &mut operators);
    operators
}

字符串

q8l4jmvw

q8l4jmvw2#

试图澄清我的问题表明,即使是我认为有效的版本实际上也不是在我想要的所有情况下都有效。
这让我找到了其他资源,比如this rust lang论坛帖子和this rust Github issue。
这让我意识到,我实际上想表达的是类似于以下两行之一的东西:

type MyOp<T: Trait> = dyn for<'a,'b> Fn(&'a mut String, &'b mut T) where T: 'b;

type MyOp = dyn for<'a,'b, T where T: Trait + 'b> Fn(&'a mut String, &'b mut T);

字符串
这显然是不正确的Rust,但其思想是,我想让T的生存期类似于type MyOp<T> = dyn for<'a,'b> Fn(&'a mut String, &'b mut T)中的显式生存期,我们应该接受满足边界的T的无穷大。这意味着T的生存期不应该在生成运算符Vec时决定,而是在调用运算符时决定。
看起来我们正在进入我不熟悉的更高类型的领域。此外,这些在Rust中是不可用的,至少在稳定的分支上是如此。
因此,我认为我的问题的答案是,如果没有一些复杂的helper traits/宏技巧,它目前是不可行的,这将使代码非常难以理解。
如果有人对我(希望)澄清的问题有比这更好的答案,我很乐意用一个简单的解决方案证明我是错的。

相关问题