Placement new is a feature currently being discussed for the Rust programming
language. It gives programmer control of memory allocation and memory
placement, where current memory allocation implementations are hidden
behind compiler internals via the Box::new interface. Controlling
memory allocation is useful in many different
applications, but a few that come to mind:
- Arena allocators: Pre-allocating big chunks of memory from the operating system up front, and arbitrating memory ownership directly in your application. This helps avoid context switches into the kernel for certain memory use-cases.
- Embedded allocators: Allocating chunks of memory from well-defined memory locations. Useful when you need memory to come from very specific hardware addresses.
This is Rust’s answer to C++ placement new, allowing one to control not only when and how memory is freed, but also where it is allocated and freed from (Thanks Holy_City for the clarification here!)
How heap allocation in Rust works now
Heap allocation is hidden behind the Box type in Rust stable. When
you instantiate data within a Box type, you’re placing the data onto
the heap, and the Box type stores an internal pointer to that heap allocated
data.
// Count will be placed on the heap
let heap_data = Box::new(Count { num: 1 });If you dig around in the source of the Box type, you can see some hints at why ‘placement new’ might be useful.
Let’s see how Box::new is implemented:
#[stable(feature = "rust1", since = "1.0.0")]
#[inline(always)]
pub fn new(x: T) -> Box<T> {
box x
}It looks like memory allocation, and lifting into the box type is hidden
behind this box keyword. Searching
up on the box keyword yields the Rust Documentation on unstable Box Syntax
and Patterns. This
unstable feature allows you to use the box keyword to instantiate
allocated Boxes directly on the global heap.
What does the
Drop
implementation for
Box look like?
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {
fn drop(&mut self) {
// FIXME: Do nothing, drop is currently
// performed by compiler.
}
}I was expecting to see some unsafe dealloc calls here, but it looks like this work is being done somewhere in compiler internals.
How Placement New Fits
If you need to customize how heap allocation works, ideally, you’d be
able to hook into the box keyword or use similar syntax (and avoid C++’esque
library solutions). This is exactly what Rust’s ‘placement new’
feature gives us. We need a way to use that fancy box syntax (or
something similar), and
implement our own memory placement strategies.
The work on ‘placement new’ is broken down into a few different efforts that need to come together to make all of this work:
- A syntax extension that allows the programmer to specify where they’d like the memory placed. (RFC#1228),
- A Placer trait that would allow custom allocation / placement
implementations, that returns a
Placetype from thePlacers requiredmake_placefunction. - Desugaring logic that transforms the syntax into straight
forward Rust code that calls the correct
Placers. - Implementations of existing implicit allocation / placement
strategies, including a
BoxPlacefor the defaultBoxheap allocation strategy.
Placement New Examples
There’s no clear consensus on how placement new syntax will work yet, but there are many options being discussed in RFC#1228. A few different options being discussed:
Overloaded ‘box’ syntax
We could overload the previously mentioned box syntax above, and
allow it to take a place expression:
let arena_ref = box arena 5;
let heap_ref = box HEAP 5;Left Arrow Syntax
Left arrow syntax,
follows the syntax PLACE_EXPR <- VALUE_EXPR:
let arena_ref = arena <- MyStruct::new();
let heap_ref = HEAP <- MyStruct::new();Placement ‘in’ syntax
Placement ‘in’ syntax uses the ‘in’ keyword to define the placement location.
let arena_ref = in arena { MyStruct::new() };
let heap_ref = in HEAP { MyStruct::new() };Placer examples
The Placer implementation for ExchangeHeapSingleton (the
default box heap heap allocation method) implementation
that
was repealed from
Rust looked something like this:
fn make_place<T>() -> IntermediateBox<T> {
let layout = Layout::new::<T>();
let p = if layout.size() == 0 {
mem::align_of::<T>() as *mut u8
} else {
unsafe {
Heap.alloc(layout.clone()).unwrap_or_else(|err| {
Heap.oom(err)
})
}
};
IntermediateBox {
ptr: p,
layout,
marker: marker::PhantomData,
}
}
impl<T> Placer<T> for ExchangeHeapSingleton {
type Place = IntermediateBox<T>;
fn make_place(self) -> IntermediateBox<T> {
make_place()
}
}And the corresponding Drop implementation:
impl<T: ?Sized> Drop for IntermediateBox<T> {
fn drop(&mut self) {
if self.layout.size() > 0 {
unsafe {
Heap.dealloc(self.ptr, self.layout.clone())
}
}
}
}In this case, a Heap type handles all the unsafe alloc / dealloc,
and the Place returned from the Placer is of type
IntermediateBox.
Current feature status
It’s not clear yet when all these things will land, especially given the uncertainty around the placement syntax. Some of the initial work that was commited to Rust unstable (including syntax extensions and the Placer protocol traits) was subsequently removed in light of further design discussions needing to be had. Either way, I think placement new is an important feature for Rust. Adding explicit (but not required) control points to the internals of Rust will make it more appealing for certain use cases, including embedded and other applications that have special memory control requirements. I’m very much looking forward to this feature landing in Rust nightly.