Skip to content

Commit

Permalink
CloneToUninit: use a private specialization trait
Browse files Browse the repository at this point in the history
and move implementation details into a submodule
  • Loading branch information
GrigorenkoPV committed Jun 26, 2024
1 parent c4139df commit 0f1cf48
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 117 deletions.
123 changes: 6 additions & 117 deletions library/core/src/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@

#![stable(feature = "rust1", since = "1.0.0")]

use crate::mem::{self, MaybeUninit};
use crate::ptr;
mod uninit;

/// A common trait for the ability to explicitly duplicate an object.
///
Expand Down Expand Up @@ -245,7 +244,7 @@ pub unsafe trait CloneToUninit {
/// * `dst` must be properly aligned.
/// * `dst` must have the same [pointer metadata] (slice length or `dyn` vtable) as `self`.
///
/// [valid]: ptr#safety
/// [valid]: crate::ptr#safety
/// [pointer metadata]: crate::ptr::metadata()
///
/// # Panics
Expand All @@ -269,83 +268,20 @@ pub unsafe trait CloneToUninit {

#[unstable(feature = "clone_to_uninit", issue = "126799")]
unsafe impl<T: Clone> CloneToUninit for T {
#[inline]
default unsafe fn clone_to_uninit(&self, dst: *mut Self) {
// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::write().
unsafe {
// We hope the optimizer will figure out to create the cloned value in-place,
// skipping ever storing it on the stack and the copy to the destination.
ptr::write(dst, self.clone());
}
}
}

// Specialized implementation for types that are [`Copy`], not just [`Clone`],
// and can therefore be copied bitwise.
#[doc(hidden)]
#[unstable(feature = "clone_to_uninit", issue = "126799")]
unsafe impl<T: Copy> CloneToUninit for T {
#[inline]
unsafe fn clone_to_uninit(&self, dst: *mut Self) {
// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::copy_nonoverlapping().
unsafe {
ptr::copy_nonoverlapping(self, dst, 1);
}
// SAFETY: we're calling a specialization with the same contract
unsafe { <T as self::uninit::CopySpec>::clone_one(self, dst) }
}
}

#[unstable(feature = "clone_to_uninit", issue = "126799")]
unsafe impl<T: Clone> CloneToUninit for [T] {
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
default unsafe fn clone_to_uninit(&self, dst: *mut Self) {
let len = self.len();
// This is the most likely mistake to make, so check it as a debug assertion.
debug_assert_eq!(
len,
dst.len(),
"clone_to_uninit() source and destination must have equal lengths",
);

// SAFETY: The produced `&mut` is valid because:
// * The caller is obligated to provide a pointer which is valid for writes.
// * All bytes pointed to are in MaybeUninit, so we don't care about the memory's
// initialization status.
let uninit_ref = unsafe { &mut *(dst as *mut [MaybeUninit<T>]) };

// Copy the elements
let mut initializing = InitializingSlice::from_fully_uninit(uninit_ref);
for element_ref in self.iter() {
// If the clone() panics, `initializing` will take care of the cleanup.
initializing.push(element_ref.clone());
}
// If we reach here, then the entire slice is initialized, and we've satisfied our
// responsibilities to the caller. Disarm the cleanup guard by forgetting it.
mem::forget(initializing);
}
}

#[doc(hidden)]
#[unstable(feature = "clone_to_uninit", issue = "126799")]
unsafe impl<T: Copy> CloneToUninit for [T] {
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
unsafe fn clone_to_uninit(&self, dst: *mut Self) {
let len = self.len();
// This is the most likely mistake to make, so check it as a debug assertion.
debug_assert_eq!(
len,
dst.len(),
"clone_to_uninit() source and destination must have equal lengths",
);

// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::copy_nonoverlapping().
unsafe {
ptr::copy_nonoverlapping(self.as_ptr(), dst.as_mut_ptr(), len);
}
// SAFETY: we're calling a specialization with the same contract
unsafe { <T as self::uninit::CopySpec>::clone_slice(self, dst) }
}
}

Expand All @@ -371,53 +307,6 @@ unsafe impl CloneToUninit for crate::ffi::CStr {
}
}

/// Ownership of a collection of values stored in a non-owned `[MaybeUninit<T>]`, some of which
/// are not yet initialized. This is sort of like a `Vec` that doesn't own its allocation.
/// Its responsibility is to provide cleanup on unwind by dropping the values that *are*
/// initialized, unless disarmed by forgetting.
///
/// This is a helper for `impl<T: Clone> CloneToUninit for [T]`.
struct InitializingSlice<'a, T> {
data: &'a mut [MaybeUninit<T>],
/// Number of elements of `*self.data` that are initialized.
initialized_len: usize,
}

impl<'a, T> InitializingSlice<'a, T> {
#[inline]
fn from_fully_uninit(data: &'a mut [MaybeUninit<T>]) -> Self {
Self { data, initialized_len: 0 }
}

/// Push a value onto the end of the initialized part of the slice.
///
/// # Panics
///
/// Panics if the slice is already fully initialized.
#[inline]
fn push(&mut self, value: T) {
MaybeUninit::write(&mut self.data[self.initialized_len], value);
self.initialized_len += 1;
}
}

impl<'a, T> Drop for InitializingSlice<'a, T> {
#[cold] // will only be invoked on unwind
fn drop(&mut self) {
let initialized_slice = ptr::slice_from_raw_parts_mut(
MaybeUninit::slice_as_mut_ptr(self.data),
self.initialized_len,
);
// SAFETY:
// * the pointer is valid because it was made from a mutable reference
// * `initialized_len` counts the initialized elements as an invariant of this type,
// so each of the pointed-to elements is initialized and may be dropped.
unsafe {
ptr::drop_in_place::<[T]>(initialized_slice);
}
}
}

/// Implementations of `Clone` for primitive types.
///
/// Implementations that cannot be described in Rust
Expand Down
128 changes: 128 additions & 0 deletions library/core/src/clone/uninit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::mem::{self, MaybeUninit};
use crate::ptr;

/// Private specialization trait used by CloneToUninit, as per
/// https://std-dev-guide.rust-lang.org/policy/specialization.html
pub(super) unsafe trait CopySpec: Clone {
unsafe fn clone_one(src: &Self, dst: *mut Self);
unsafe fn clone_slice(src: &[Self], dst: *mut [Self]);
}

unsafe impl<T: Clone> CopySpec for T {
#[inline]
default unsafe fn clone_one(src: &Self, dst: *mut Self) {
// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::write().
unsafe {
// We hope the optimizer will figure out to create the cloned value in-place,
// skipping ever storing it on the stack and the copy to the destination.
ptr::write(dst, src.clone());
}
}

#[inline]
#[cfg_attr(debug_assertions, track_caller)]
default unsafe fn clone_slice(src: &[Self], dst: *mut [Self]) {
let len = src.len();
// This is the most likely mistake to make, so check it as a debug assertion.
debug_assert_eq!(
len,
dst.len(),
"clone_to_uninit() source and destination must have equal lengths",
);

// SAFETY: The produced `&mut` is valid because:
// * The caller is obligated to provide a pointer which is valid for writes.
// * All bytes pointed to are in MaybeUninit, so we don't care about the memory's
// initialization status.
let uninit_ref = unsafe { &mut *(dst as *mut [MaybeUninit<T>]) };

// Copy the elements
let mut initializing = InitializingSlice::from_fully_uninit(uninit_ref);
for element_ref in src {
// If the clone() panics, `initializing` will take care of the cleanup.
initializing.push(element_ref.clone());
}
// If we reach here, then the entire slice is initialized, and we've satisfied our
// responsibilities to the caller. Disarm the cleanup guard by forgetting it.
mem::forget(initializing);
}
}

// Specialized implementation for types that are [`Copy`], not just [`Clone`],
// and can therefore be copied bitwise.
unsafe impl<T: Copy> CopySpec for T {
#[inline]
unsafe fn clone_one(src: &Self, dst: *mut Self) {
// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::copy_nonoverlapping().
unsafe {
ptr::copy_nonoverlapping(src, dst, 1);
}
}

#[inline]
#[cfg_attr(debug_assertions, track_caller)]
unsafe fn clone_slice(src: &[Self], dst: *mut [Self]) {
let len = src.len();
// This is the most likely mistake to make, so check it as a debug assertion.
debug_assert_eq!(
len,
dst.len(),
"clone_to_uninit() source and destination must have equal lengths",
);

// SAFETY: The safety conditions of clone_to_uninit() are a superset of those of
// ptr::copy_nonoverlapping().
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), len);
}
}
}

/// Ownership of a collection of values stored in a non-owned `[MaybeUninit<T>]`, some of which
/// are not yet initialized. This is sort of like a `Vec` that doesn't own its allocation.
/// Its responsibility is to provide cleanup on unwind by dropping the values that *are*
/// initialized, unless disarmed by forgetting.
///
/// This is a helper for `impl<T: Clone> CloneToUninit for [T]`.
struct InitializingSlice<'a, T> {
data: &'a mut [MaybeUninit<T>],
/// Number of elements of `*self.data` that are initialized.
initialized_len: usize,
}

impl<'a, T> InitializingSlice<'a, T> {
#[inline]
fn from_fully_uninit(data: &'a mut [MaybeUninit<T>]) -> Self {
Self { data, initialized_len: 0 }
}

/// Push a value onto the end of the initialized part of the slice.
///
/// # Panics
///
/// Panics if the slice is already fully initialized.
#[inline]
fn push(&mut self, value: T) {
MaybeUninit::write(&mut self.data[self.initialized_len], value);
self.initialized_len += 1;
}
}

impl<'a, T> Drop for InitializingSlice<'a, T> {
#[cold] // will only be invoked on unwind
fn drop(&mut self) {
let initialized_slice = ptr::slice_from_raw_parts_mut(
MaybeUninit::slice_as_mut_ptr(self.data),
self.initialized_len,
);
// SAFETY:
// * the pointer is valid because it was made from a mutable reference
// * `initialized_len` counts the initialized elements as an invariant of this type,
// so each of the pointed-to elements is initialized and may be dropped.
unsafe {
ptr::drop_in_place::<[T]>(initialized_slice);
}
}
}

0 comments on commit 0f1cf48

Please sign in to comment.