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>
两次(或者如果外部闭包多次调用其第一个参数),就会导致恐慌。