Blake Smith

create. code. learn.

»

What is 'Placement New' in Rust?

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:

  1. 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.
  2. 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 Place type from the Placers required make_place function.
  • 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 BoxPlace for the default Box heap 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.


about the author

Blake Smith is a Principal Software Engineer at Sprout Social.