使用arg函数的Rust函数

问题描述 投票:4回答:2

我想写一个通用函数count_calls,它调用一个函数f,它接受一个函数指针(lambda),其中count_calls计算函数f调用给定lambda函数的频率。

我努力与这种方法(Playground)。

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>可能是一个解决方案。但如果可能的话,我更喜欢只有堆栈的解决方案。

rust function-pointers
2个回答
2
投票

这是我设法工作的最简单的代码(playground):

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

4
投票

错误“(dyn std::ops::Fn() -> S + 'static)类型的值的大小在编译时无法知道”是由你的F特征引起的:

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

这相当于F: Sized + FnMut(dyn Fn() -> S)。这意味着闭合F将按值获取特征对象(dyn Fn() -> S)。但特征对象是未分级的,不能通过值传递(尚未)。

一种解决方案是通过引用或在Box中传递特征对象。 The answer by rodrigo解释并讨论了这些解决方案。


我们可以避免特质对象和动态调度吗?

我认为不正确。

Non solutions

一个想法是将另一个类型参数添加到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始终是我们自己的封闭类型。所以这不起作用。

我们想要的是通用闭包(我们可以选择它的类型参数)。类似的东西是可能的,但仅限于寿命参数。它被称为HRTBs,看起来像F: for<'a> Fn(&'a u32)。但它在这里没有用,因为我们需要一个类型参数而且for<T>不存在(还是?)。

Sub-optimal, nightly solution

一种解决方案是不使用闭包,而是使用已知名称实现FnMut的类型。可悲的是,你不能在稳定的情况下为你自己的类型实现Fn*特征。 On nightly, this works

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)可以在未来适当地解决这个问题。但是,我认为还有一些不同的功能缺乏;特别是,我认为提议的GAT不会解决这个问题。

因此,如果这是一个现在需要解决的现实问题,我会:

  • 退一步,重新考虑更大范围内的问题,以避免这种Rust限制,或者
  • 只需使用动态调度。
© www.soinside.com 2019 - 2024. All rights reserved.