带有 arg 函数的 Rust 函数
Rust function which takes function with arg a function
我想编写一个通用函数 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 Whosebug {
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>
可能是一个解决方案。但如果可能的话,我更喜欢纯堆栈解决方案。
这是我设法开始工作的最简单的代码 (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 Whosebug {
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
是不可取的(因为您正在更改 public 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()
}
错误“类型 (dyn std::ops::Fn() -> S + 'static)
的值的大小在编译时无法得知 ” 是由 F
的特征绑定引起的:
F: Sized + FnMut(Fn() -> S) -> ()
这相当于F: Sized + FnMut(dyn Fn() -> S)
。这意味着闭包 F
将按值获取特征对象 (dyn Fn() -> S
)。但是 trait 对象没有大小,不能按值传递(目前)。
一个解决方案是通过引用或在 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*
特征。 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
}
不幸的是,现在您的 public 接口中有这种奇怪的类型(应该是实现细节)。
除此之外,我想不出任何真正的解决方案(只有其他超级冗长的解决方案有很多缺点)。类型系统角落的发展(特别是关于 GAT 和 HKT)可以在未来妥善解决这个问题。但是,我认为仍然缺少一些不同的功能;特别是,我认为提议的 GAT 不会解决这个问题。
因此,如果这是一个现实生活中需要立即解决的问题,我会:
- 退后一步,在更大的范围内重新思考这个问题,或许可以避免这种 Rust 限制,或者
- 只需使用动态调度。
我想编写一个通用函数 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 Whosebug {
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>
可能是一个解决方案。但如果可能的话,我更喜欢纯堆栈解决方案。
这是我设法开始工作的最简单的代码 (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 Whosebug {
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
是不可取的(因为您正在更改 public 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()
}
错误“类型 (dyn std::ops::Fn() -> S + 'static)
的值的大小在编译时无法得知 ” 是由 F
的特征绑定引起的:
F: Sized + FnMut(Fn() -> S) -> ()
这相当于F: Sized + FnMut(dyn Fn() -> S)
。这意味着闭包 F
将按值获取特征对象 (dyn Fn() -> S
)。但是 trait 对象没有大小,不能按值传递(目前)。
一个解决方案是通过引用或在 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*
特征。 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
}
不幸的是,现在您的 public 接口中有这种奇怪的类型(应该是实现细节)。
除此之外,我想不出任何真正的解决方案(只有其他超级冗长的解决方案有很多缺点)。类型系统角落的发展(特别是关于 GAT 和 HKT)可以在未来妥善解决这个问题。但是,我认为仍然缺少一些不同的功能;特别是,我认为提议的 GAT 不会解决这个问题。
因此,如果这是一个现实生活中需要立即解决的问题,我会:
- 退后一步,在更大的范围内重新思考这个问题,或许可以避免这种 Rust 限制,或者
- 只需使用动态调度。