Closed
Description
After discussion in issue #16, I propose:
- Adding a default implementation for allocation of ZSTs
- Splitting
alloc
into two separate functions for allocating ZSTs and sized types.
The former design decision is inspired by a well-defined approach to allocating ZSTs, that reduces the workload for trait implementors.
The latter design decision is grounded in Rust's philosophy of providing zero-cost abstractions. The if layout.size() == 0
branch in alloc
would impose a performance penalty for runtime variable allocations. Hence, we offer the ability to make bare metal calls to alloc_sized
and alloc_zero_sized_type
for performant use cases.
trait AllocRef {
fn alloc_sized_type(self, layout: NonZeroLayout) -> Result<NonNull<u8>, AllocErr>;
#[inline(always)]
fn alloc(self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
if layout.size() == 0 {
// We want to use NonNull::dangling here, but that function uses mem::align_of::<T>
// internally. For our use-case we cannot call dangling::<T>, since we are not generic
// over T; we only have access to the Layout of T. Instead we re-implement the
// functionality here.
//
// See https://github.com/rust-lang/rust/blob/9966af3/src/libcore/ptr/non_null.rs#L70
// for the reference implementation.
let ptr = align as *mut u8;
Ok(unsafe { NonNull::new_unchecked(ptr) })
} else {
self.alloc_sized_type(unsafe { NonZeroLayout::from_layout_unchecked(layout) })
}
}
}
Design questions
If we want to make a data-driven decision, we could run benchmarks to determine the actual cost of the additional branch on runtime variable allocations.