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

2024-03-24

我有一个实现该特征的结构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,len from fn 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?

I tried Arc<dyn Fn(dyn FnOnce(&mut [u8]), usize) -> Result<(), ()> + Send + Sync>但这行不通,因为dyn Fn必须具有在编译时已知大小的参数。

另外,还有一个小问题:如何返回-> smoltcp::Result<R> in OnVirtualTunWrite if OnVirtualTunWrite不可能知道R?


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

那应该是&dyn FnOnce(...),但这也行不通,因为调用FnOnce自动移动它,因此不能从引用后面调用它。最简单的解决方案是引入额外的分配consume, 因为Box<dyn FnOnce>实施FnOnce itself 自 Rust 1.35 起 https://users.rust-lang.org/t/can-i-call-a-box-fnonce/778/4。例如 ():

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

为了避免分配,您可以使用以下技术此处描述 https://stackoverflow.com/a/40965033/1600898调用FnOnce从一个FnMut。它用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.

毯子实现可以通过变异而不是消耗来逃脱self因为它使用Option::take将内容移出选项,同时将其留空并以其他方式可用。不消耗该选项允许call_once_safe被调用从FnMut闭包,例如在consume并作为参数传递给OnVirtualTunWrite. call_once_safe确实消耗了实际的FnOnce闭包包含在Option,从而保留闭包调用不超过一次的不变性。如果consume打电话call_once_safe一样的Option<F>两次(或者如果外部闭包多次调用其第一个参数),就会导致恐慌。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何以闭包作为参数调用闭包 的相关文章

随机推荐