I've been thinking about whether there's a happy medium between the typed and highly monomorphized storages api that was proposed in this post: Is custom allocators the right abstraction? and the existing Allocator
trait currently in nightly.
I think I've come up with an elegant solution that sticks with being byte-focused yet allows for a very compact inline Vec
(as well as other collections).
The main idea is to add an associated type "handle" to the Allocator
trait along with a function that extracts a pointer + length out of that handle.
pub unsafe trait Allocator {
// In most cases, this will just be `NonNull<[u8]>`.
type Handle: Clone;
fn get(&self, handle: &Self::Handle) -> NonNull<[u8]>;
fn default_handle(&self) -> Self::Handle;
fn allocate(&self, layout: Layout) -> Result<Self::Handle, AllocError>;
unsafe fn deallocate(&self, handle: &Self::Handle, layout: Layout);
// ...
}
In the majority of cases, Allocator::Handle
will just be NonNull<[u8]>
, so the usage doesn't change except that the collection must only store the handle and reacquire the pointer every time it's used.
I've implemented a proof-of-concept InlineAlloc
along with a simple Vec
that can use this new Allocator
trait.
This is what the usage ends up looking like:
let mut v: Vec<i32, InlineAlloc<{ 4 * 4 }>> = Vec::new_in(InlineAlloc::default());
assert_eq!(mem::size_of_val(&v), 24);
assert_eq!(v.capacity(), 4);
v.try_push(1).unwrap();
v.try_push(2).unwrap();
v.try_push(3).unwrap();
v.try_push(4).unwrap();
v.try_push(5).unwrap_err();
assert_eq!(&[1, 2, 3, 4] as &[_], v.as_slice());
println!("{:?}", v.as_slice());
Here's a link to a playground containing all the relevant code: Rust Playground.