首页 > 解决方案 > 如何以闭包作为参数调用闭包

问题描述

我有一个结构,它实现了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是一个应该接收f,lenfromfn consume然后像这样使用它的闭包:

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在编译时必须知道大小的参数。

另外,还有一个小问题:如果不可能知道,我-> smoltcp::Result<R>该如何返回?OnVirtualTunWriteOnVirtualTunWriteR

标签: rust

解决方案


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

应该是&dyn FnOnce(...),但这也不起作用,因为调用会FnOnce自动移动它,所以不能从引用后面调用它。最简单的解决方案是在 中引入额外的分配consume,因为从 Rust 1.35 开始Box<dyn FnOnce>实现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();
    }
}

为避免分配,您可以使用此处描述的技术FnOnceFnMut. 它使用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到一个中,并通过一个私有trait上的方法Option调用它,该方法带有一个毯子 impl for 。一揽子实现将闭包移出并调用它。这是允许的,因为闭包的大小在毯子实现中是已知的,这是通用的 over 。call_once_safeCallOnceSafeOption<T: FnOnce(...)>OptionT

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


推荐阅读