我可以创建一个指向堆栈对象的拥有指针

时间:2016-11-30 20:03:36

标签: rust

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

据我所知,只要调度程序没有超过任务,这应该是安全的。有没有办法让这种情况发生?

4 个答案:

答案 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);

playground

中的完整工作示例

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

Playground

但是,添加多个闭包时会遇到问题,因为它们都有不同的类型。

如果您愿意在夜间编译器上允许分配不稳定的功能,则可以使用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();
}

Playground

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

然后我可以传递生活在堆栈上的特征对象,但可以通过引用来消费。