Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I create an owned pointer to a stack object

Tags:

rust

I would like to pass a FnOnce closure to an object to be used later, but I would like to avoid any heap allocation. I can avoid heap allocation by keeping the closure on the stack. But the problem is that I can't pass a reference to the object because the FnOnce call_once consumes the closure. So I need to pass an owned pointer (e.g. Box) without heap allocation.

Is this possible? What I'd like to do is this:

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

As far as I know this should be safe as long as the scheduler doesn't outlive the task. Is there any way to make this happen?

like image 437
awelkie Avatar asked Nov 30 '16 20:11

awelkie


2 Answers

Can I create an owned pointer to a stack object?

No. This is non-sensical actually, since by definition a stack object is owned by the stack, so it cannot also be owned by something else.

So I need to pass an owned pointer (e.g. Box) without heap allocation.

There are other owned pointers than Box.

At the moment, I know of none without a heap allocation, but there is little reason it cannot be done.

I envision a InlineFnOnceBox<S: Default, R, A> used as InlineFnOnceBox<[u8; 48], (), ()> in this case, which would contain both the array itself, used as backing storage, plus a virtual pointer to the FnOnce<A -> R> v-table for the type instantiated.

It requires some care (and unsafe code) to instantiate, but otherwise seems feasible.

like image 57
Matthieu M. Avatar answered Sep 28 '22 08:09

Matthieu M.


Can I create an owned pointer to a stack object?

No, but you can simply move the stack object into your scheduler. Your scheduler will increase in size with every closure you schedule, but it will be completely self contained an can even be moved around.

The basic idea is that your Scheduler becomes a kind of singly linked list:

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)()
    }
}

The Scheduler trait is here to break the recursion chain in the NoHeapScheduler (Otherwise we'd need a feature like variadic generics).

To terminate the chain we also implement Scheduler for some no-op type, e.g. ():

impl Scheduler for () {
    fn run(self) {}
}

Now the only thing left is a way to add new closures.

impl<F: FnOnce(), T: Scheduler> NoHeapScheduler<F, T> {
    fn add_task<F2: FnOnce()>(self, f: F2) -> NoHeapScheduler<F2, Self> {
        NoHeapScheduler {
            inner: self,
            f: f,
        }
    }
}

This method moves The current scheduler into a new scheduler and adds the scheduled closure.

You can use this function like so:

let scheduler = scheduler.add_task(task);

Fully working example in the playground

like image 38
oli_obk Avatar answered Sep 28 '22 08:09

oli_obk