如何以闭包为参数调用闭包 [英] How to call closure with closure as argument
问题描述
我有一个结构体,它实现了 A
特性,它具有 fn 消耗
函数.我想将回调传递给这个结构,由 fn 消费
调用.像这样:
I have an struct that implements the trait A
which has the function fn consume
. I want to pass a callback to this struct, to be called by fn consume
. Something like this:
pub type OnVirtualTunWrite = Arc
它在一个 Arc
上,因为它在线程之间共享.
It's on an Arc
because it's shared between threads.
struct A {
on_virtual_tun_write: OnVirtualTunWrite
}
impl S for A {
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> smoltcp::Result<R>
where
F: FnOnce(&mut [u8]) -> smoltcp::Result<R>,
{
let mut lower = self.lower.as_ref().borrow_mut();
//I should send this f to self.on_virtual_tun_write
(self.on_virtual_tun_write)(f, len);
//return the appropriate result here
OnVirtualTunWrite
是一个闭包,它应该从 fn 消费
接收 f,len
然后像这样使用它:
OnVirtualTunWrite
is a closure that should receive the f,len
from fn consume
and then use it like this:
let my_on_virtual_tun_write = Arc::new(|?, len| -> ?{
let mut buffer = Vec::new(len);
buffer.resize(len);
//fills buffer with data
f(buffer);
})
如何制作我的 OnVirtualTunWrite
?
我试过 Arc
但它不会工作,因为 dyn Fn
必须有在编译时知道大小的参数.
I tried Arc<dyn Fn(dyn FnOnce(&mut [u8]), usize) -> Result<(), ()> + Send + Sync>
but it won't work because dyn Fn
must have arguments with size know at compile time.
另外,还有一个小问题:如何返回->
如果 OnVirtualTunWrite
中的 smoltcp::ResultOnVirtualTunWrite
不可能知道 R
?
Also, there's still a small problem: how do I return -> smoltcp::Result<R>
in OnVirtualTunWrite
if OnVirtualTunWrite
can't possibly know R
?
推荐答案
我试过
Arc
结果<()、()>+ 发送 + 同步>
那应该是 &dyn FnOnce(...)
,但这也不起作用,因为调用 FnOnce
会自动移动它,所以它不能从引用后面调用.最简单的解决方案是在 consume
中引入额外的分配,因为 Box
自己实现了 FnOnce
自 Rust 1.35 起.例如(playground):
That should be &dyn FnOnce(...)
, but that won't work either because calling FnOnce
automatically moves it, so it can't be called from behind a reference. The simplest solution is to introduce an extra allocation in consume
, because Box<dyn FnOnce>
implements FnOnce
itself since Rust 1.35. For example (playground):
pub type OnVirtualTunWrite = Arc<
dyn Fn(Box<dyn FnOnce(&mut [u8])>, usize) -> Result<(), VirtualTunWriteError> + Send + Sync,
>;
pub struct A {
pub on_virtual_tun_write: OnVirtualTunWrite,
}
impl A {
pub fn consume<F>(&self, f: F)
where
F: FnOnce(&mut [u8]) + 'static,
{
(self.on_virtual_tun_write)(Box::new(f), 0).unwrap();
}
}
为了避免分配,您可以使用此处描述的技术来调用 FnOnce
一个 FnMut
.它使用 Option
而不是 Box
,所以它是零成本的,或者至少是免分配的.例如(完整代码在操场上):
To avoid the allocation, you can use the technique described here to invoke FnOnce
from an FnMut
. It uses Option
rather than Box
, so it's zero-cost, or at least allocation-free. For example (full code in the playground):
pub type OnVirtualTunWrite = Arc<
dyn Fn(&mut dyn FnMut(&mut [u8]), usize) -> Result<(), VirtualTunWriteError> + Send + Sync,
>;
trait CallOnceSafe {
fn call_once_safe(&mut self, x: &mut [u8]);
}
impl<F: FnOnce(&mut [u8])> CallOnceSafe for Option<F> {
fn call_once_safe(&mut self, x: &mut [u8]) {
// panics if called more than once - but A::consume() calls it
// only once
let func = self.take().unwrap();
func(x)
}
}
impl A {
pub fn consume<F>(&self, f: F)
where
F: FnOnce(&mut [u8]) + 'static,
{
let mut f = Some(f);
(self.on_virtual_tun_write)(&mut move |x| f.call_once_safe(x), 0).unwrap();
}
}
上述工作首先将 FnMut
移动到 Option
中,然后通过 call_once_safe
(私有 上的方法)调用它CallOnceSafe
特性,带有 Option
的全面实现.整体实现将闭包移出 Option
并调用它.这是允许的,因为闭包的大小在笼统的实现中是已知的,这在 T
上是通用的.
The above works by first moving the FnMut
into an Option
, and calling it through call_once_safe
, a method on a private CallOnceSafe
trait with a blanket impl for Option<T: FnOnce(...)>
. The blanket implementation moves the closure out of the Option
and invokes it. This is allowed because the size of the closure is known in the blanket implementation, which is generic over T
.
整体实现可以通过变异而不是消耗 self
逃脱,因为它使用 Option::take
将内容移出选项,同时将其留空并否则可用.不使用该选项允许从 FnMut
闭包调用 call_once_safe
,例如在 consume
中创建并作为参数传递给 的闭包OnVirtualTunWrite
.call_once_safe
确实消耗了包含在 Option
中的实际 FnOnce
闭包,从而保留了闭包被调用不超过一次的不变性.如果 consume
对同一个 Option
调用 call_once_safe
两次(或者如果外部闭包多次调用它的第一个参数),这会导致恐慌.
The blanket impl can get away with mutating rather than consuming self
because it uses Option::take
to move the content out of the option, while leaving it empty and otherwise usable. Not consuming the option allows call_once_safe
to be called from an FnMut
closure, such as the one created in consume
and passed as argument to OnVirtualTunWrite
. call_once_safe
does consume the actual FnOnce
closure contained in the Option
, thus preserving the invariant that the closure is called no more than once. If consume
were to call call_once_safe
on the same Option<F>
twice (or if the outer closure called its first argument more than once), it would result in a panic.
这篇关于如何以闭包为参数调用闭包的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!