如何以闭包为参数调用闭包 [英] How to call closure with closure as argument

查看:52
本文介绍了如何以闭包为参数调用闭包的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个结构体,它实现了 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结果<(), VirtualTunWriteError>+ 发送 + 同步>;

它在一个 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结果<()、()>+ Send + Sync> 但它不会工作,因为 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::Result 如果 OnVirtualTunWrite 不可能知道 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆