我想将FnOnce
闭包传递给稍后要使用的对象,但我想避免任何堆分配。我可以通过将闭包保持在堆栈上来避免堆分配。但问题是我无法传递对象的引用,因为FnOnce
call_once
使用了闭包。所以我需要在没有堆分配的情况下传递一个拥有的指针(例如Box
)。
这可能吗?我想做的是:
fn main() {
let mut scheduler = NoHeapScheduler();
// allocate the task on the stack
let task = move ||;
// somehow pass ownership of the closure, while keeping it allocated on the stack.
scheduler.add_task(StaticBox::new(task));
schedule.run();
}
据我所知,只要调度程序没有超过任务,这应该是安全的。有没有办法让这种情况发生?
答案 0 :(得分:4)
我可以创建一个指向堆栈对象的拥有指针吗?
没有。实际上这是非感性的,因为根据定义,堆栈对象由堆栈拥有,因此它不能也拥有其他东西。
所以我需要在没有堆分配的情况下传递一个拥有的指针(例如Box)。
还有其他指针而不是Box
。
目前,没有堆分配,我知道没有,但没有理由不能做到。
在这种情况下,我设想InlineFnOnceBox<S: Default, R, A>
用作InlineFnOnceBox<[u8; 48], (), ()>
,它包含数组本身,用作后备存储,以及指向FnOnce<A -> R>
v-table的虚拟指针实例化的类型。
它需要一些小心(和unsafe
代码)来实例化,但在其他方面似乎是可行的。
答案 1 :(得分:3)
我可以创建一个指向堆栈对象的拥有指针吗?
不,但您只需将堆栈对象移动到调度程序中即可。您安排的每个闭包都会增加您的调度程序的大小,但它将完全自包含,甚至可以移动。
基本思想是您的计划程序成为一种单链表:
pub trait Scheduler: Sized {
fn run(self);
}
pub struct NoHeapScheduler<F: FnOnce(), T: Scheduler> {
inner: T,
f: F,
}
impl<F: FnOnce(), T: Scheduler> Scheduler for NoHeapScheduler<F, T> {
fn run(self) {
self.inner.run();
(self.f)()
}
}
此Scheduler
特征是为了打破NoHeapScheduler
中的递归链(否则我们需要像变量泛型一样的特征)。
要终止链条,我们还会针对某些无操作类型实施Scheduler
,例如()
:
impl Scheduler for () {
fn run(self) {}
}
现在唯一剩下的就是添加新闭包的方法。
impl<F: FnOnce(), T: Scheduler> NoHeapScheduler<F, T> {
fn add_task<F2: FnOnce()>(self, f: F2) -> NoHeapScheduler<F2, Self> {
NoHeapScheduler {
inner: self,
f: f,
}
}
}
此方法将当前调度程序移动到新的调度程序并添加计划的闭包。
您可以像这样使用此功能:
let scheduler = scheduler.add_task(task);
中的完整工作示例
答案 2 :(得分:2)
如上所述,问题的答案是&#34; no&#34;。
如果您通过了关闭的所有权,您必须按照定义将其移动到所有者(否则您获得的是参考)。如果您只使用泛型类型进行一次回调,则可以执行此操作:
pub struct NoHeapScheduler<F:FnOnce()> {
f: Option<F>,
}
impl<F:FnOnce()> NoHeapScheduler<F> {
pub fn add_task(&mut self, f: F) {
self.f = Some(f);
}
pub fn run(&mut self) {
let f = self.f.take().unwrap();
f()
}
}
fn main() {
let mut scheduler = NoHeapScheduler{ f: None };
let task = move || {};
scheduler.add_task(task);
scheduler.run();
}
但是,添加多个闭包时会遇到问题,因为它们都有不同的类型。
如果您愿意在夜间编译器上允许分配和不稳定的功能,则可以使用FnBox
。这是like FnOnce
but works with Box
:
#![feature(fnbox)]
use std::boxed::FnBox;
pub struct NoHeapScheduler {
v: Vec<Box<FnBox()>>,
}
impl NoHeapScheduler {
pub fn add_task(&mut self, f: Box<FnBox()>) {
self.v.push(f);
}
pub fn run(&mut self) {
for f in self.v.drain(0..) {
f();
}
}
}
fn main() {
let mut scheduler = NoHeapScheduler{ v: Vec::new() };
let task = move || {println!("Hello,"); };
let other_task = move || {println!("world!"); };
scheduler.add_task(Box::new(task));
scheduler.add_task(Box::new(other_task));
scheduler.run();
}
答案 3 :(得分:0)
我可以使用Option
来执行此操作。我可以将Option
保留在堆栈上并传递一个可变引用,然后当我准备好使用闭包时,我可以使用Option::take
获取闭包的所有权并使用它。
要处理FnOnce
的不同实现,我可以将其解析为特征并使用特征对象:
trait Callable {
fn call(&mut self);
}
impl<F: FnOnce()> Callable for Option<F> {
fn call(&mut self) {
if let Some(func) = self.take() {
func();
}
}
}
然后我可以传递生活在堆栈上的特征对象,但可以通过引用来消费。