Skip to content

Commit

Permalink
rust: init: add write_[pin_]init functions
Browse files Browse the repository at this point in the history
Sometimes it is necessary to split allocation and initialization into
two steps. One such situation is when reusing existing allocations
obtained via `Box::drop_contents`. See [1] for an example.

In order to support this use case add `write_[pin_]init` functions to the
pin-init API. These functions operate on already allocated smart
pointers that wrap `MaybeUninit<T>`.

Link: https://lore.kernel.org/rust-for-linux/[email protected]/ [1]
Signed-off-by: Benno Lossin <[email protected]>
Reviewed-by: Boqun Feng <[email protected]>
Reviewed-by: Alice Ryhl <[email protected]>
Reviewed-by: Gary Guo <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Miguel Ojeda <[email protected]>
Signed-off-by: Paolo Bonzini <[email protected]>
(cherry picked from commit 6d1c22d0ace31d096b0dab5318c6a0d3219d6456)
  • Loading branch information
Benno Lossin authored and y86-dev committed Nov 22, 2024
1 parent 218a675 commit 6841b61
Showing 1 changed file with 63 additions and 25 deletions.
88 changes: 63 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,27 +1137,15 @@ impl<T> InPlaceInit<T> for Box<T> {
where
E: From<AllocError>,
{
let mut this = Box::try_new_uninit()?;
let slot = this.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() }.into())
Box::try_new_uninit()?.write_pin_init(init)
}

#[inline]
fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
where
E: From<AllocError>,
{
let mut this = Box::try_new_uninit()?;
let slot = this.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() })
Box::try_new_uninit()?.write_init(init)
}
}

Expand All @@ -1168,29 +1156,79 @@ impl<T> InPlaceInit<T> for Arc<T> {
where
E: From<AllocError>,
{
let mut this = Arc::try_new_uninit()?;
let slot = unsafe { Arc::get_mut_unchecked(&mut this) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized and this is the only `Arc` to that data.
Ok(unsafe { Pin::new_unchecked(this.assume_init()) })
Arc::try_new_uninit()?.write_pin_init(init)
}

#[inline]
fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
where
E: From<AllocError>,
{
let mut this = Arc::try_new_uninit()?;
let slot = unsafe { Arc::get_mut_unchecked(&mut this) };
Arc::try_new_uninit()?.write_init(init)
}
}

/// Smart pointer containing uninitialized memory and that can write a value.
pub trait InPlaceWrite<T> {
/// The type `Self` turns into when the contents are initialized.
type Initialized;

/// Use the given initializer to write a value into `self`.
///
/// Does not drop the current value and considers it as uninitialized memory.
fn write_init<E>(self, init: impl Init<T, E>) -> Result<Self::Initialized, E>;

/// Use the given pin-initializer to write a value into `self`.
///
/// Does not drop the current value and considers it as uninitialized memory.
fn write_pin_init<E>(self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E>;
}

#[cfg(feature = "alloc")]
impl<T> InPlaceWrite<T> for Box<MaybeUninit<T>> {
type Initialized = Box<T>;

fn write_init<E>(mut self, init: impl Init<T, E>) -> Result<Self::Initialized, E> {
let slot = self.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { self.assume_init() })
}

fn write_pin_init<E>(mut self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E> {
let slot = self.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { self.assume_init() }.into())
}
}

#[cfg(feature = "alloc")]
impl<T> InPlaceWrite<T> for Arc<MaybeUninit<T>> {
type Initialized = Arc<T>;

fn write_init<E>(mut self, init: impl Init<T, E>) -> Result<Self::Initialized, E> {
let slot = unsafe { Arc::get_mut_unchecked(&mut self) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() })
Ok(unsafe { self.assume_init() })
}

fn write_pin_init<E>(mut self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E> {
let slot = unsafe { Arc::get_mut_unchecked(&mut self) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized and this is the only `Arc` to that data.
Ok(unsafe { Pin::new_unchecked(self.assume_init()) })
}
}

Expand Down

0 comments on commit 6841b61

Please sign in to comment.