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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With