Возврат рекурсивного замыкания в Rust - PullRequest
3 голосов
/ 18 апреля 2019

У меня есть следующая функция высшего порядка

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

Моя цель - вернуть эту лямбду.Я не могу понять, как эффективно использовать 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 Ответ

6 голосов
/ 18 апреля 2019

Вы используете лот ссылок, которые не кажутся необходимыми, и вам все сложнее понять это:

  1. A 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 {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

В разделе 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> в аргументах функции (см. Почему не рекомендуется принимать ссылку на строку (& String), Vec (& Vec) или Box (& Box) в качестве аргумента функции? ).
...