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.

3
  • 2
    Why would you take the function pointer as a reference? play.rust-lang.org/… Commented Apr 18, 2019 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. Commented Apr 18, 2019 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 Commented Apr 19, 2019 at 7:03

1 Answer 1

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?).
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.