在Rust中返回递归关闭

时间:2019-04-18 15:10:20

标签: recursion reference rust closures lifetime

我具有以下高阶函数

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 个答案:

答案 0 :(得分:6)

您正在使用很多引用,这些引用似乎不是必需的,因此很难一一理解:

  1. fn已经 一个 pointer 函数,因此您可以按值传递它们,而不必使用另一层引用。因为函数指针为'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 {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

help部分将告诉您确切的解决方法:使闭包move成为其环境。结果是:

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)
    }
}

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


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

  1. 使用表达式代替return关键字。
  2. 在函数参数中使用&[f64]代替&Vec<f64>(请参见Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?)。