5

I have the following higher-order function

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

My goal is to return this lambda. I can't figure out how to effectively use tone_fn here though.

The code above errors out:

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`

If I try to include a lifetime though, I am not sure how to type impl Fn, and include the lifetime

// where do I write `'a`?
fn ensure_tonicty<'a>(tone_fn: &'a fn(&f64, &f64) -> bool) -> impl Fn(&Vec<f64>) -> bool {

I could write this as a macro and get past this, but I'm curious if there's a way I can do this without going the macro route.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Stepan Parunashvili
  • 2,627
  • 5
  • 30
  • 51
  • 2
    Why would you take the function pointer as a reference? https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=716761593981eea73885173e5aefb861 – mcarton Apr 18 '19 at 16:45
  • 1
    You have a lot of references to `Copy` types, which definitely make this harder. The other thing to know is that you _always_ must use a `move` closure if you are going to return it from another function. Otherwise it will be referencing variables that are no longer in scope. – Peter Hall Apr 18 '19 at 17:29
  • thank you for the help all, and the detailed explanation @PeterHall! Indeed, I used a bunch of references -- was hacking away and anticipated the type checker a bit too much – Stepan Parunashvili Apr 19 '19 at 07:03

1 Answers1

7

You are using a lot of references, which don't seem necessary, and make it harder to figure this all out:

  1. A fn is already a function pointer, so you can pass them around by value instead of using another layer of references. This is easier because a function pointer is 'static.
  2. All those &f64s are immutable, so could be replaced with f64 without changing the logic. This should be the same speed as (or possibly faster than) using a reference.

Once you do that, you won't have many reference left, and it will be clearer which are causing the problem:

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

Now, the error is:

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 {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The help section tells you exactly how to fix it: make the closure move its environment. The result is:

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

If you return a closure from another function, you will nearly always need this keyword. Otherwise, any variables mentioned in the closure will be references to values that will go out of scope when the function ends. Using the move keyword moves those values so they go wherever the closure goes.


Also notice the other changes I made, to make the code more idiomatic:

  1. Use expressions instead of return keyword.
  2. Use &[f64] instead of &Vec<f64> in function arguments (see Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?).
Peter Hall
  • 53,120
  • 14
  • 139
  • 204