Four levels of in-place initialization — Yosh Wuyts — Blog
Author<br>Yosh Wuyts
Published on<br>2026-08-15
Samples licensed as<br>Apache 2.0
Citation
Four levels of in-place initialization
Introduction<br>The goal of in-place initialization is to enable the construction of types<br>directly into a memory location without any additional moves or copies. When<br>working with big types this can be more efficient and even prevent stack<br>overflows. But some types are what we call address sensitive and so cannot be moved for correctness reasons.
There is some disagreement about how we should encode in-place initialization in<br>the language. There are conflicting requirements and constraints at play, and<br>reconciling those is tricky. I believe that the right way to attack the<br>problem space is not by introducing a single feature, but by introducing a<br>4-level feature hierarchy for in-place initialization.
Level 0: Raw pointers<br>At the lowest level we have raw pointers and MaybeUninit. This is by far the<br>most flexible way to encode emplacement, but it comes at the cost of virtually<br>everything else. This is both how the pin-init crate and placing crate are<br>implemented internally. To read more about this see my post on placing<br>functions where I work through<br>a full desugaring.
The way I categorize this level is as: “It’s better than nothing”. It’s good<br>that we have some way to encode emplacement in the ecosystem today, even if it<br>leaves much to be desired. Here is a basic example using MaybeUninit, raw<br>pointers, and unsafe to defer initialization:
rust<br>use std::mem::MaybeUninit;
let mut x = MaybeUninit::A>::uninit(); // 1. Create an uninit place `x` of type `A`<br>let y: *mut A = x.as_mut_ptr(); // 2. Take a raw pointer `y` to `x`<br>unsafe { y.write(A { .. }) }; // 3. Initialize all fields through `y`<br>let mut x = unsafe { x.assume_init() }; // 5. Notarize `x` as initialized<br>let y: &mut A = &mut x; // 6. `x` is initialized and can be used as normalCopy<br>On step 5 we do move the value of x. If wanted to notarize x as initialized<br>without moving it, we would need to call MaybeUninit::assume_init_mut, but<br>this returns an &mut T rather than change T in-place. Without additional<br>language features, it’s impossible to notarize an owned value as initialized<br>without moving it or turning it into a reference.
Level 1: References<br>Raw pointers are very powerful, but the compiler cannot check their correctness<br>which places an additional burden on the programmer. What we need is an<br>abstraction that can encode most of what raw pointers can, but in a way that the<br>compiler can statically check it within a reasonable amount of time.
My preferred proposal for this is Ding Xiang Fei’s &uninit / &own reference<br>pair, but there are more proposals that could fill this slot. The idea of<br>&uninit/&own that we can take an &uninit reference to a type, and once all<br>of its fields have been initialized can then be notarized into an &own<br>reference. There are four steps to the process here:
Create some uninit place x of type A.
Take a &uninit reference to x.
Initialize the value, giving you back an &own reference.
Notarize the initialization via assignment.
rust<br>let x: A; // 1. Create an uninit place `x` of type `A`<br>let y: &uninit A = &x; // 2. Take an `&uninit` reference `y` to `x`<br>*y = A { .. }; // 3. Initialize all fields of `y`<br>let y: &own A = y; // 4. The reference `y` is `&own` from here on out<br>x = y; // 5. Notarize `x` as initialized<br>let y: &mut A = &mut x; // 6. `x` is now initialized and can be used as normalCopy<br>The main innovation of this proposal is that it makes uninitialized places a<br>first-class thing we can talk about and reference. The example above can already<br>be written today without &uninit and &own by writing let a; a = A { ... };.<br>But this doesn’t work across functions, which is something we can do with &uninit/&own:
rust<br>// Convert an `&uninit A` into an `&own A`.<br>fn init_a'a>(y: &'a uninit A) -> &'a own A {<br>*y = A { ... };
let x: A; // 1. Create an uninit place `x` of type `A`<br>x = init_a(&x); // 2. Initialize `x`<br>let y: &mut A = &mut x; // 3. `x` is now initialized and can be used as normalCopy<br>This is not a simple feature, but it’s not a simple problem either. This makes<br>uninitialized values both first-class and safe to pass around and<br>initialize. By design it wants to be as expressive as possible, which means prioritizing<br>control above all else.
Level 2: Placing Functions<br>Where references prioritize control, placing functions prioritize ergonomics.<br>Placing functions are<br>functions which re-write the return keyword to write data to an out-pointer<br>rather than copying. It can be implemented in terms of either raw pointers or<br>&uninit/&own references. But unlike either of those features it doesn’t<br>require any further changes to the function signature.
To show where this is useful we need to think about how we would transition<br>existing code to emplace. Here is a typical function which returns a value of<br>type A, and assigns it to the variable...