在Rust中返回一个递归闭包

5

我有以下高阶函数

fn ensure_tonicty(tone_fn: &fn(&f64, &f64) -> bool) -> impl Fn(&Vec<f64>) -> bool {
    return |floats: &Vec<f64>| -> bool {
        let first = floats.first().unwrap();
        let rest = &floats[1..];
        fn f(tone_fn: &fn(&f64, &f64) -> bool, prev: &f64, xs: &[f64]) -> bool {
            match xs.first() {
                Some(x) => tone_fn(prev, x) && f(tone_fn, x, &xs[1..]),
                None => true,
            }
        };
        return f(tone_fn, first, rest);
    };
}

我的目标是返回这个 lambda 表达式。但我不知道如何有效地使用 tone_fn

上面的代码会报错:

error[E0621]: explicit lifetime required in the type of `tone_fn`
 --> src/lib.rs:1:56
  |
1 | fn ensure_tonicty(tone_fn: &fn(&f64, &f64) -> bool) -> impl Fn(&Vec<f64>) -> bool {
  |                            -----------------------     ^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime `'static` required
  |                            |
  |                            help: add explicit lifetime `'static` to the type of `tone_fn`: `&'static for<'r, 's> fn(&'r f64, &'s f64) -> bool`

如果我尝试包含一个生命周期,我不确定该如何输入impl Fn并包含这个生命周期。
// where do I write `'a`?
fn ensure_tonicty<'a>(tone_fn: &'a fn(&f64, &f64) -> bool) -> impl Fn(&Vec<f64>) -> bool {

我可以写成宏来解决这个问题,但我想知道是否有其他方法可以不用宏来实现。


1
你有很多对 Copy 类型的引用,这肯定会让事情变得更加困难。另外需要知道的是,如果你要从另一个函数中返回闭包,那么你 必须 使用 move 闭包。否则它将引用不再在作用域内的变量。 - Peter Hall
谢谢@PeterHall的帮助和详细解释!我确实使用了很多参考资料——一直在瞎搞,对类型检查器的预期有点过高。大家都帮了我很多,感谢! - Stepan Parunashvili
1个回答

7
你使用了很多参考,它们似乎是不必要的并且使得这一切更难以理解:
  1. fn 已经是函数指针,所以可以通过值传递而不是使用另一层引用来传递。这样更容易,因为函数指针是 `static` 的。
  2. 所有这些 `&f64` 都是不可变的,所以可以用 `f64` 替换,而不改变逻辑。这应该与(或有可能比)使用引用更快。
一旦你这样做,你就不会有太多的引用了,也会更清楚哪些引用造成了问题。
fn ensure_tonicty(tone_fn: fn(f64, f64) -> bool) -> impl Fn(&Vec<f64>) -> bool {
    |floats: &Vec<f64>| -> bool {
        let first = *floats.first().unwrap();
        let rest = &floats[1..];
        fn f(tone_fn: fn(f64, f64) -> bool, prev: f64, xs: &[f64]) -> bool {
            match xs.first() {
                Some(&x) => tone_fn(prev, x) && f(tone_fn, x, &xs[1..]),
                None => true,
            }
        };
        f(tone_fn, first, rest);
    };
}

现在,出现了以下错误:
error[E0373]: closure may outlive the current function, but it borrows `tone_fn`, which is owned by the current function
  --> src/lib.rs:2:12
   |
2  |     return |floats: &Vec<f64>| -> bool {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `tone_fn`
...
11 |         return f(tone_fn, first, rest);
   |                  ------- `tone_fn` is borrowed here
   |
note: closure is returned here
  --> src/lib.rs:2:12
   |
2  |       return |floats: &Vec<f64>| -> bool {
   |  ____________^
3  | |         let first = *floats.first().unwrap();
4  | |         let rest = &floats[1..];
5  | |         fn f(tone_fn: fn(f64, f64) -> bool, prev: f64, xs: &[f64]) -> bool {
...  |
11 | |         return f(tone_fn, first, rest);
12 | |     };
   | |_____^
help: to force the closure to take ownership of `tone_fn` (and any other referenced variables), use the `move` keyword
   |
2  |     return move |floats: &Vec<f64>| -> bool {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

“帮助”部分告诉您如何解决它:使闭包“移动”其环境。结果是:

fn ensure_tonicty(tone_fn: fn(f64, f64) -> bool) -> impl Fn(&[f64]) -> bool {
    move |floats: &[f64]| -> bool {
        let first = floats[0];
        let rest = &floats[1..];
        fn f(tone_fn: fn(f64, f64) -> bool, prev: f64, xs: &[f64]) -> bool {
            match xs.first() {
                Some(&x) => tone_fn(prev, x) && f(tone_fn, x, &xs[1..]),
                None => true,
            }
        };
        f(tone_fn, first, rest)
    }
}

如果你从另一个函数返回闭包,几乎总是需要使用this关键字,否则在闭包中提到的任何变量都将是引用,这些引用将在函数结束时超出范围。 使用move关键字可以将这些值移动到闭包所在的位置。


还要注意我所做的其他更改,以使代码更符合惯用法:

  1. 在函数参数中使用表达式而不是return关键字。
  2. 在函数参数中使用&[f64]而不是&Vec<f64>(请参见为什么不建议接受对String(&String)、Vec(&Vec)或Box(&Box)的引用作为函数参数?)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接