我有一个实现特征的结构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
两次(或者如果外部闭包多次调用其第一个参数),则会导致恐慌。