Skip to content

[lib] In-place initialization infrastructure #142518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@ language_item_table! {

MaybeUninit, sym::maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None;

Init, sym::init_trait, init_trait, Target::Trait, GenericRequirement::Exact(1);
InitError, sym::init_error, init_error, Target::AssocTy, GenericRequirement::Exact(1);
InitLayout, sym::init_layout, init_layout, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::Exact(1);
InitInit, sym::init_init, init_init, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::Exact(1);

Termination, sym::termination, termination, Target::Trait, GenericRequirement::None;

Try, sym::Try, try_trait, Target::Trait, GenericRequirement::None;
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,11 @@ symbols! {
infer_static_outlives_requirements,
inherent_associated_types,
inherit,
init,
init_error,
init_init,
init_layout,
init_trait,
inlateout,
inline,
inline_const,
Expand Down
93 changes: 93 additions & 0 deletions library/core/src/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! In-place initialization.
//!
//! This module describes the interface through which types supporting in-place initialization
//! can perform initialization with minimal or zero additional allocations or moves.

use crate::ptr::Pointee;

/// In-place Initializer for `T`.
///
/// An instance of `Init<T>` carries all the information necessary to initialize a `T` in a
/// well-defined memory location, criteria of which is prescribed in the Safety section.
///
/// # Fallibility
///
/// The initialization might fail and return an error of type [`Self::Error`] instead.
/// In that case, the memory provided to [`Self::init`] shall be repurposed in any way,
/// even though it might have been written to by this initializer.
///
/// # Examples
///
/// ## Initializing unsized types
///
/// To initialize an unsized type, first query the required layout for `T` using [`Self::layout`].
/// Then provide a pointer to an allocation of at least the specified alignment and size.
///
/// If initialization was successful, then [`Self::init`] returns the metadata that combined with
/// the pointer to the given to [`Self::init`] yields a valid pointer to `T`.
///
/// ``` ignore (illustrative)
/// use std::alloc::alloc;
/// fn init_boxed<T: ?Sized + Pointee, I: Init<T>>(init: I) -> Result<Box<T>, I::Error> {
/// let layout = init.layout();
/// let memory = alloc(layout).cast::<()>();
/// let meta = init.init(memory)?;
/// Box::from_raw(from_raw_parts_mut(memory, meta))
/// }
/// ```
///
/// # Safety
///
/// Implementers must ensure that if [`self.init(slot)`] returns `Ok(metadata)`,
/// then [`core::ptr::from_raw_parts_mut(slot, metadata)`] must reference a valid
/// value owned by the caller.
/// Furthermore, the layout returned by using
/// [`core::intrinsics::size_of_val`] and [`core::intrinsics::align_of_val`] on this pointer
/// must match what [`Self::layout()`] returns exactly.
///
/// If `T` is sized, or in other words `T: Sized`, [`Init::layout`] in this case *must*
/// return the same layout as [`Layout::new::<T>()`] would.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, we should require that if T: Sized, Init::layout returns exactly Layout::new::<T>().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied

/// Implementers must ensure that the implementation of `init()` does not rely
/// on the value being pinned.
///
/// [`core::ptr::from_raw_parts_mut(slot, metadata)`]: core::ptr::from_raw_parts_mut
/// [`Self::layout()`]: Init::layout
/// [`self.init(slot)`]: Init::init
/// [`Layout::new::<T>()`]: core::alloc::Layout::new
#[unstable(feature = "in_place_initialization", issue = "999999")]
#[lang = "init_trait"]
pub unsafe trait Init<T: ?Sized + Pointee> {
/// Error type upon initialization failure during the actual
/// in-place initialization procedure.
#[lang = "init_error"]
type Error;

/// The layout needed by this initializer.
/// This method must return a layout that precisely matches
/// with `T`.
/// Namely the size and the alignment must be equal.
#[lang = "init_layout"]
fn layout(&self) -> crate::alloc::Layout;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming it's not allowed to have layout being larger than actually result T::Metadata, then they are actually duplicated information and one implies the other. This means we the results of layout() and init() introduce an invariant of the relationship between them. It also makes the resulting metadata depending on the dynamic result of init() while it should not.

Would it be better to only expose the layout/metadata in a single place, like replacing layout with fn dest_metadata(&self) -> T::Metadata; and make unsafe fn init(self, slot: *mut ()) -> Result<(), Self::Error>; or even with slot: *mut T to ease implementations?

Then the trait user would do let layout = Layout::for_value_raw(std::ptr::from_raw_parts(slot, init.ptr_metadata()));. It also self-explains the reason why dynamic layout is needed when we already have {size,align}_of*. It had this confusion on the first glace of the trait Init definition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the metadata should be able to depend on what init does. For example, I could decide to initialize a dyn MyTrait with either Foo or Bar that is dynamically decided by init.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the metadata should be able to depend on what init does. For example, I could decide to initialize a dyn MyTrait with either Foo or Bar that is dynamically decided by init.

Then T = dyn MyTrait, and you need to branch in both layout and init anyway, and their results are expected to agree each other. But I agree slot: *mut () works better in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay fair. But how do you get the alignment & size from the metadata? I didn't understand what you wrote above:

Then the trait user would do let layout = Layout::for_value_raw(std::ptr::from_raw_parts(slot, init.ptr_metadata()));.

You don't have slot at that point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling layout on the same initializer must always return the same value, and init must initialize a value whose layout matches it exactly. So the choice of layout must happen before init runs, and it's not okay to have layout require size=8, align=8 and then have init write a [u8; 8] into the slot.

Copy link
Contributor Author

@dingxiangfei2009 dingxiangfei2009 Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to ask for a clarification. Did you mean the other way around, like layout only requiring size=8 and align=1 like a [u8; 8] but then init write a size=8, align=8 value whose type has to work with align=8? That is the problematic case we want to reject.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That case is wrong too, but I meant what I said. The layout must be an exact match - it's not okay for Init::layout to return a layout that is too large or overaligned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay fair. But how do you get the alignment & size from the metadata? I didn't understand what you wrote above:

Then the trait user would do let layout = Layout::for_value_raw(std::ptr::from_raw_parts(slot, init.ptr_metadata()));.

You don't have slot at that point.

Oh, I didn't realize that there is no method to get a Layout from T: Pointee and a T::Metadata instance directly. We can forge a NULL pointer for Layout::for_value_raw, which meets its safety condition of having a valid metadata part. MIRI also passes on playground. Not sure if it worth an addition/change to Layout methods.

unsafe fn layout_from_meta<T: ptr::Pointee + ?Sized>(meta: T::Metadata) -> Layout {
    unsafe {
        // wait, if `for_value_raw` only care about ptr metadata, why doesn't it just
        // accept a `T::Metadata` parameter?
        Layout::for_value_raw(ptr::from_raw_parts::<T>(ptr::null::<()>(), meta))
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the comment to make it very specific.


/// Writes a valid value of type `T` to `slot` or fails.
///
/// If this call returns [`Ok`], then `slot` is guaranteed to contain a valid
/// value of type `T`.
/// Otherwise, in case the result is an [`Err`], the implementation must guarantee
/// that the `slot` may be repurposed for future reuse.
///
/// If `T` is unsized, then `slot` may be combined with
/// the metadata to obtain a valid pointer to the value.
///
/// Note that `slot` should be thought of as a `*mut T`. A unit type is used
/// so that the pointer is thin even if `T` is unsized.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing the part that if it returns Err, then the slot can be repurposed in any way (though it might have been written to).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied with additional comments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot to add the hunk to the change set.

I would like to make it more explicit. Maybe it will become a future OpSem question. Should we make the Err case to be, as if the slot is populated with poison value so that a read from this slot pointer is UB? In effect, the implementation would act as if, upon exiting with Err, a StorageDead is called on the entire slot.

I think we can defer it for further clarification. We can come back to update the documentation at any time before calling for stabilisation.

/// # Safety
///
/// The caller must provide a pointer that references a location that `init`
/// may write to, and the location must have at least the size and alignment
/// specified by [`Init::layout`].
#[lang = "init_init"]
unsafe fn init(self, slot: *mut ()) -> Result<T::Metadata, Self::Error>;
}
4 changes: 4 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ pub mod task;
#[allow(missing_docs)]
pub mod alloc;

/* In-place initialization */
#[unstable(feature = "in_place_initialization", issue = "999999")]
pub mod init;

// note: does not need to be public
mod bool;
mod escape;
Expand Down
Loading