如何用闭包作为参数来调用闭包



我有一个实现特征的结构A它具有函数fn consume.我想将回调传递给这个结构,由fn consume调用。像这样:

pub type OnVirtualTunWrite = Arc<dyn Fn(?, usize) -> Result<(), VirtualTunWriteError> + Send + Sync>;

它在Arc上,因为它在线程之间共享。

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 consume接收f,len,然后像这样使用它:

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(dyn FnOnce(&mut [u8]), usize) -> Result<(), ()> + Send + Sync>但它不起作用dyn Fn因为在编译时必须具有大小知道的参数。

此外,还有一个小问题:如果OnVirtualTunWrite不可能知道R,我如何在OnVirtualTunWrite中返回-> smoltcp::Result<R>

我试过Arc<dyn Fn(dyn FnOnce(&mut [u8]), usize) -> Result<(), ()> + Send + Sync>

这应该是&dyn FnOnce(...)的,但这也不起作用,因为调用FnOnce会自动移动它,因此不能从引用后面调用它。最简单的解决方案是在consume中引入额外的分配,因为Box<dyn FnOnce>从 Rust 1.35 开始实现FnOnce。例如(游乐场):

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

若要避免分配,可以使用此处描述的技术从FnMut调用FnOnce。它使用Option而不是Box,因此它是零成本的,或者至少是无分配的。例如(操场上的完整代码):

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<T: FnOnce(...)>。一揽子实现将闭包移出Option并调用它。这是允许的,因为闭包的大小在一揽子实现中是已知的,这是通用的T

一揽子 impl 可以侥幸变异而不是消耗self,因为它使用Option::take将内容移出选项,同时将其留空并可用。不使用该选项允许从FnMut闭包调用call_once_safe,例如在consume中创建并作为参数传递给OnVirtualTunWrite的闭包。call_once_safe确实消耗了包含在Option中的实际FnOnce闭包,从而保持了闭包被调用不超过一次的不变性。如果consume对同一Option<F>调用call_once_safe两次(或者如果外部闭包多次调用其第一个参数),则会导致恐慌。

最新更新