Функция Rust, которая принимает функцию с функцией arg - PullRequest
4 голосов
/ 18 апреля 2019

Я хочу написать обобщенную функцию count_calls, которая вызывает функцию f, которая принимает указатель функции (лямбда), где count_calls считает, как часто функция f вызывает данную лямбда-функцию.

Я борюсь с подходом ( Детская площадка ).

fn count_calls<S, F>(s: S, f: F) -> u32
where
    S: Clone,
    F: Sized + FnMut(Fn() -> S) -> (),
{
    let mut counter: u32 = 0;

    f(|| {
        counter += 1;
        s.clone()
    });
    counter
}

#[cfg(test)]
mod stackoverflow {
    use super::*;

    fn f(p: fn() -> i32) {
        p();
        p();
    }

    #[test]
    fn test() {
        let counts = count_calls(3, f);
        assert_eq!(counts, 2);
    }
}

Здесь я получаю сообщение об ошибке:

error[E0277]: the size for values of type `(dyn std::ops::Fn() -> S + 'static)` cannot be known at compilation time
  --> src/lib.rs:1:1
   |
1  | / fn count_calls<S, F>(s: S, f: F) -> u32
2  | | where
3  | |     S: Clone,
4  | |     F: Sized + FnMut(Fn() -> S) -> (),
...  |
12 | |     counter
13 | | }
   | |_^ doesn't have a size known at compile-time
   |
   = help: within `((dyn std::ops::Fn() -> S + 'static),)`, the trait `std::marker::Sized` is not implemented for `(dyn std::ops::Fn() -> S + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required because it appears within the type `((dyn std::ops::Fn() -> S + 'static),)`
   = note: required by `std::ops::FnMut`

Кто-нибудь знает, как это исправить?

[Изменить]

Я думаю, что использование Box<Fn()->S> может быть решением. Но я бы предпочел решение только со стеком, если это возможно.

Ответы [ 2 ]

4 голосов
/ 19 апреля 2019

Ошибка « размер для значений типа (dyn std::ops::Fn() -> S + 'static) не может быть известна во время компиляции » вызвана вашей чертой, привязанной к F:

F: Sized + FnMut(Fn() -> S) -> ()

Этоэквивалентно F: Sized + FnMut(dyn Fn() -> S).Это означает, что замыкание F будет принимать объект признака (dyn Fn() -> S) по значению.Но объекты черт не имеют размера и не могут быть переданы по значению (пока).

Одним из решений было бы передать объект признака по ссылке или в Box. Ответ Родриго объясняет и обсуждает эти решения.


Можем ли мы избежать объектов-черт и динамической диспетчеризации?

Думаю, не совсем.

Не решения

Одной из идей было бы добавить другой параметр типа к count_calls:

fn count_calls<S, F, G>(s: S, f: F) -> u32
where
    S: Clone,
    F: Sized + FnMut(G),
    G: Fn() -> S,

Однако это не работает:

error[E0308]: mismatched types
  --> src/lib.rs:9:7
   |
9  |       f(|| {
   |  _______^
10 | |         counter += 1;
11 | |         s.clone()
12 | |     });
   | |_____^ expected type parameter, found closure
   |
   = note: expected type `G`
              found type `[closure@src/lib.rs:9:7: 12:6 counter:_, s:_]`

Проблема здесь в том, что аргументы типа count_calls выбираются вызывающей стороной count_calls.Но мы на самом деле хотим, чтобы G всегда был типом нашего собственного замыкания.Так что это не работает.

Нам нужно общее закрытие (где мы можем выбрать его параметры типа).Нечто подобное возможно, но ограничено параметрами времени жизни.Он называется HRTB и выглядит как F: for<'a> Fn(&'a u32).Но это здесь не помогает, потому что нам нужен параметр типа, а for<T> не существует (пока?).

Неоптимальное, ночное решение

Одним из решений было бы неиспользуйте замыкание, но тип с известным именем, который реализует FnMut.К сожалению, вы не можете реализовать черты Fn* для своего собственного типа в стабильной версии. По ночам это работает .

struct CallCounter<S> {
    counter: u32,
    s: S,
}
impl<S: Clone> FnOnce<()> for CallCounter<S> {
    type Output = S;
    extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
        // No point in incrementing the counter here
        self.s
    }
}
impl<S: Clone> FnMut<()> for CallCounter<S> {
    extern "rust-call" fn call_mut(&mut self, _: ()) -> Self::Output {
        self.counter += 1;
        self.s.clone()
    }
}

fn count_calls<S, F>(s: S, mut f: F) -> u32
where
    S: Clone,
    F: Sized + FnMut(&mut CallCounter<S>),     // <----
{
    let mut counter = CallCounter {
        counter: 0,
        s,
    };

    f(&mut counter);   // <-------

    counter.counter
}

К сожалению, теперь у вас есть этот странный тип в вашем публичном интерфейсе (который должен быть деталью реализации).


Кроме того, я не могу придумать ни одного реального решения (только другие супер-подробные решения с множеством недостатков).События в углу системы типов (в частности, в отношении GAT и HKT) могут решить эту проблему должным образом в будущем.Тем не менее, я думаю, что по-прежнему не хватает нескольких различных функций;в частности, я не думаю, что предложенные ГАТ уже решат эту проблему.

Так что, если это реальная проблема, которую нужно решить прямо сейчас, я бы:

  • отступил бы и переосмыслил проблему в большем объеме, чтобы, возможно, избежать этого ограничения Rustили
  • просто используйте динамическую диспетчеризацию.
2 голосов
/ 18 апреля 2019

Это самый простой код, который мне удалось получить ( детская площадка ):

fn count_calls<S, F>(s: S, mut f: F) -> u32
where
    S: Clone,
    F: FnMut(&mut dyn FnMut() -> S) -> (),
{
    let mut counter: u32 = 0;

    f(&mut || {
        counter += 1;
        s.clone()
    });
    counter
}

#[cfg(test)]
mod stackoverflow {
    use super::*;

    fn f(p: &mut dyn FnMut() -> i32) {
        p();
        p();
    }

    #[test]
    fn test() {
        let counts = count_calls(3, f);
        assert_eq!(counts, 2);
    }
}

Главное изменение состоит в том, что аргумент функции для F изменяется с Fn() -> S в &mut dyn FnMut() -> S.Вам нужна ссылка, потому что вы используете динамическую диспетчеризацию.Также вам нужен FnMut, потому что вы захватываете counter и изменяете его внутри, а Fn не позволяет этого.

Обратите внимание, что вы не можете использовать Box<FnMut() -> S.Это не позволит захватывать ссылку на counter, потому что в штучной упаковке функции должны быть 'static.

Если вы обнаружите, что изменение Fn на FnMut нежелательно (потому что вы меняете свой публичный API)Вы можете вернуться к F: FnMut(&mut dyn Fn() -> S) -> (), определив счетчик как Cell<u32>:

fn count_calls<S, F>(s: S, mut f: F) -> u32
where
    S: Clone,
    F: FnMut(&dyn Fn() -> S) -> (),
{
    let counter: Cell<u32> = Cell::new(0);

    f(&|| {
        counter.set(counter.get() + 1);
        s.clone()
    });
    counter.into_inner()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...