A subset of "placement new" without nightly!

The core idea is UninitProject.

/// A type with determined layout.
/// It can be projected to another type with the same fields,
/// but all the fields are not initialized yet.
pub unsafe trait UninitProject<U>: Sized {
    /// Projects a type to its uninitialized mirror.
    fn uninit_project(this: &mut MaybeUninit<Self>) -> &mut U;
}

Just project types. You don't need to care about raw pointers' behavior.

Project an array

Array layout is determined. It's safe to project MaybeUninit<[T; N]> to [MaybeUninit<T>; N].

// unsafe impl<T, const N: usize> UninitProject<[MaybeUninit<T>; N]> for [T; N] {
//     fn uninit_project(this: &mut MaybeUninit<Self>) -> &mut [MaybeUninit<T>; N] {
//         unsafe { &mut *this.as_mut_ptr().cast() }
//     }
// }

impl_UninitProject_for_array!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,);
impl_UninitProject_for_array!(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,);
impl_UninitProject_for_array!(64, 128, 256, 512, 1024, 2048, 4096,);

Project a struct

For example, here is a large struct,

#[repr(C)]
pub struct State {
    a: u64,
    b: String,
    c: HashMap<String, String>,
    d: [Vec<u32>; 16],
    e: [u8; 4096],
}

and its mirror type.

#[repr(C)]
pub struct __UninitStruct__State {
    a: MaybeUninit<u64>,
    b: MaybeUninit<String>,
    c: MaybeUninit<HashMap<String, String>>,
    d: MaybeUninit<[Vec<u32>; 16]>,
    e: MaybeUninit<[u8; 4096]>,
}

The layout of State is determined because of repr(C).

So __UninitStruct__State has exactly the same layout with State.

It's safe to project MaybeUninit<State> to __UninitStruct__State:

unsafe impl UninitProject<__UninitStruct__State> for State {
    fn uninit_project(this: &mut MaybeUninit<Self>) -> &mut __UninitStruct__State {
        unsafe { &mut *this.as_mut_ptr().cast() }
    }
}

Unsafe code can trust UninitProject's behavior because it's an unsafe trait.

When Rust 1.51 becomes stable, you may no longer need to project a struct. std::ptr::addr_of_mut! can create a mut raw pointer to an uninitialized place.

Project an enum

A repr(C) enum can be projected to multiple variant types.

#[allow(clippy::large_enum_variant)]
#[repr(C)]
pub enum State {
    A,
    B(String),
    C(u32, u32),
    D { name: String, value: String },
    E { data: [u8; 4096] },
}

The corresponding types:

#[repr(C)]
pub enum __UninitEnumDiscriminant__State {
    A,
    B,
    C,
    D,
    E,
}

#[repr(C)]
pub struct __UninitEnumVariant__State__A;

#[repr(C)]
pub struct __UninitEnumVariant__State__B(
    MaybeUninit<String>
);

#[repr(C)]
pub struct __UninitEnumVariant__State__C(
    MaybeUninit<u32>,
    MaybeUninit<u32>,
);

#[repr(C)]
pub struct __UninitEnumVariant__State__D {
    name: MaybeUninit<String>,
    value: MaybeUninit<String>,
}

#[repr(C)]
pub struct __UninitEnumVariant__State__E {
    data: MaybeUninit<[u8; 4096]>,
}

When projecting an uninitialized enum to one of its variants, you must write the correct tag to its place.

pub unsafe fn split_enum<T, P>(base: *mut ()) -> (*mut T, *mut P) {
    let base = base.cast::<u8>();
    let tag = base.cast::<T>();
    let payload = base.add(core::mem::size_of::<T>()).cast::<P>();
    (tag, payload)
}

unsafe impl UninitProject<__UninitEnumVariant__State__B> for State {
    fn uninit_project(
        this: &mut MaybeUninit<Self>,
    ) -> &mut __UninitEnumVariant__State__B {
        type Tag = __UninitEnumDiscriminant__State;
        type Payload = __UninitEnumVariant__State__B;
        unsafe {
            let base = this.as_mut_ptr().cast();
            let (tag, payload) = split_enum::<Tag, Payload>(base);
            tag.write(Tag::B);
            &mut *payload
        }
    }
}

Initializing an enum and switching between variants can be so easy.

impl State {
    pub fn init_a(this: &mut MaybeUninit<Self>) {
        uninit_project!(this => enum State => A);
    }

    pub fn init_d(this: &mut MaybeUninit<Self>, name: String, value: String) {
        let this = uninit_project!(this => enum State => D);
        overwrite(&mut this.name, name);
        overwrite(&mut this.value, value);
    }

    pub fn init_e(this: &mut MaybeUninit<Self>) {
        let this = uninit_project!(this => enum State => E);
        memset_zeroed(&mut this.data);
    }

    pub fn reset_to_a(&mut self) {
        let this: *mut Self = self;
        unsafe {
            ptr::drop_in_place(this);
            Self::init_a(&mut *this.cast())
        }
    }

    pub fn new_boxed_e() -> Box<Self> {
        unsafe { Box::emplace_zeroed_by(Self::init_e) }
    }
}

The placement-new crate

placement-new provides some macros, functions and traits to help generate the types and simplify the implementation.

All of them works without nightly!

(I'm not sure whether my implementation is sound or not.)

Crate: https://crates.io/crates/placement-new

Docs: https://docs.rs/placement-new

GitHub: https://github.com/Nugine/placement-new

Examples: https://github.com/Nugine/placement-new/tree/main/examples/src

3 Likes