Module static_alloc::doc::e3_pinned
source · Expand description
Safely pinning a statically allocated task.
§Usage — Pinned tasks
Another use case similar to a pool is the ability to pin objects that have
been allocated without the need for macros or unsafe
. This is sound because
the memory within a static bump allocator is never reused for any other
purpose.
Let’s set the scene by defining a long running task that requires pinning.
use static_alloc::Bump;
async fn example(x: usize) -> usize {
// Holding reference across yield point.
// This requires pinning to run this future.
let y = &x;
core::future::pending::<()>().await;
*y
}
Note that the type of this asynchronous future, the state machine synthesized
as the result type of the function example
, is self-referential because it
holds a reference to x
, assigned to y
, at an await point. This means that
this type is also !Unpin
. Once it is polled at a particular location in
memory it mustn’t move. This is enforced by requiring you to pin it before
polling the future, a kind of state that ensures the memory used for
representing the type can not be reused until after its destructor (Drop
)
was called.
Ordinarily there are several unsafe
constructors for Pin<&mut _>
and a few
helper macros that, in one way or another, ensure you can’t access it by value
after pinning it, and that you can’t forget
it either. There is also an
entirely safe method available only when a global allocator exists: A value
within a Box<_>
can be pinned at will by calling Box::into_pin
. This is
safe because the memory can not be reused before it is deallocated, which can
only happen by dropping the box itself which necessarily drops the value as
well.
Since a static Bump
is also an allocator, an argument similar to a globally
allocated Box
holds for values put into its memory! If ensure that the bump
allocator was borrowed forever, that is for the 'static
lifetime, then it can
not be reset (because this requires &mut
/unique access) and no memory is ever
reused. This allows using a pool instead of the global allocator. This is
particularly interesting for tasks that will run exactly once or a bounded
number of times. In this case there is no risk that this will eventually
exhaust the allocator’s memory because memory is never returned.
§Pinning an allocated task
It is time to demonstrate this in code:
use core::pin::Pin;
use static_alloc::{Bump, leaked::LeakBox};
// A generous size estimation.
// See below for a genius exact idea.
// On nightly you could calculate the size as a constant.
static SLOT: Bump<[usize; 4]> = Bump::uninit();
let future: LeakBox<'static, _> = SLOT.leak_box(example(0))
.expect("Our size estimation was generous enough");
let mut future: Pin<_> = LeakBox::into_pin(future);
let can_use_this_in_async = async move {
let _ = future.as_mut().await;
};
Let me repeat how that this will of course only work the first time, after that
the bump allocator might be exhausted. You can also only pin values that were
leak-boxed on a static
pool of memory, since this is the required assurance
that the memory is never reused. This means that we will mostly want to use
this for creating an instance of a single global task.
Nevertheless, the code above guarantee that the task is properly Drop
’d in
benign circumstances. This is in contrast to actually leaking the value where
it would never be dropped. It’s sound not to forget a pinned future
constructed in this way but it it shouldn’t happen by accident in decently
well-written code.
§Calculate the required allocation for a future’s state machine
The following idea by @Yandros calculates the layout of a future. We can then create a memory reservation large enough to guarantee that one of that future can be allocated.
use core::{alloc::Layout, marker::PhantomData};
use static_alloc::Bump;
struct Helper<F>(PhantomData<F>);
impl<F> Helper<F> {
const fn layout(self: Helper<F>) -> Layout {
Layout::new::<F>()
}
fn use_the_type_inference_luke(self: &'_ Self, _: &'_ F)
{}
}
async fn test() {}
const LAYOUT_OF_TEST: Layout = {
let h = Helper(PhantomData);
let _ = || {
h.use_the_type_inference_luke(&test())
};
h.layout()
};
// Number of bytes to guarantee one address is aligned.
const REQUIRED_TO_GUARANTEE: usize = LAYOUT_OF_TEST.size() + LAYOUT_OF_TEST.align();
static SLOT: Bump<[u8; REQUIRED_TO_GUARANTEE]> = Bump::uninit();
SLOT.leak_box(test())
.expect("This _must_ work the first time it is called");