首页 文章

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

提问于
浏览
6

我想将 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 回答

  • 3

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

    不 . 实际上这是非感性的,因为根据定义,堆栈对象由堆栈拥有,因此它也不能由其他东西拥有 .

    所以我需要在没有堆分配的情况下传递一个拥有的指针(例如Box) .

    除了 Box 之外还有其他拥有的指针 .

    目前,没有堆分配,我知道没有,但没有理由不能做到 .

    在这种情况下,我设想 InlineFnOnceBox<S: Default, R, A> 用作 InlineFnOnceBox<[u8; 48], (), ()> ,它包含数组本身,用作后备存储,以及指向实例化类型的 FnOnce<A -> R> v表的虚拟指针 .

    它需要一些小心(和 unsafe 代码)来实例化,但在其他方面似乎是可行的 .

  • 4

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

    不,但您可以简单地将堆栈对象移动到调度程序中 . 您安排的每个闭包都会增加您的调度程序的大小,但它将完全自包含,甚至可以移动 .

    基本思想是您的Scheduler成为一种单链表:

    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

  • 0

    如上所述,问题的答案是“不” .

    如果你传递了闭包的所有权,你必须按照定义将它移动到所有者(否则你得到的是一个参考) . 如果您只使用泛型类型进行一次回调,则可以这样做:

    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

  • 2

    我可以使用 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();
            }
        }
    }
    

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

相关问题