about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/core/src/clone.rs127
-rw-r--r--library/core/src/clone/uninit.rs128
-rw-r--r--library/core/tests/clone.rs40
-rw-r--r--library/std/src/ffi/os_str.rs13
-rw-r--r--library/std/src/ffi/os_str/tests.rs17
-rw-r--r--library/std/src/lib.rs1
-rw-r--r--library/std/src/path.rs12
-rw-r--r--library/std/src/path/tests.rs19
-rw-r--r--library/std/src/sys/os_str/bytes.rs13
-rw-r--r--library/std/src/sys/os_str/wtf8.rs13
-rw-r--r--library/std/src/sys_common/wtf8.rs12
11 files changed, 290 insertions, 105 deletions
diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs
index 76a89eaaff8..21504630672 100644
--- a/library/core/src/clone.rs
+++ b/library/core/src/clone.rs
@@ -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.
 ///
@@ -248,7 +247,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
@@ -272,124 +271,42 @@ pub unsafe trait CloneToUninit {
 
 #[unstable(feature = "clone_to_uninit", issue = "126799")]
 unsafe impl<T: Clone> CloneToUninit for T {
-    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.
-#[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);
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: we're calling a specialization with the same contract
+        unsafe { <T as self::uninit::CopySpec>::clone_slice(self, dst) }
     }
 }
 
 #[unstable(feature = "clone_to_uninit", issue = "126799")]
-unsafe impl<T: Copy> CloneToUninit for [T] {
+unsafe impl CloneToUninit for str {
+    #[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: str is just a [u8] with UTF-8 invariant
+        unsafe { self.as_bytes().clone_to_uninit(dst as *mut [u8]) }
     }
 }
 
-/// 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);
-        }
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for crate::ffi::CStr {
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: For now, CStr is just a #[repr(trasnsparent)] [c_char] with some invariants.
+        // And we can cast [c_char] to [u8] on all supported platforms (see: to_bytes_with_nul).
+        // The pointer metadata properly preserves the length (NUL included).
+        // See: `cstr_metadata_is_length_with_nul` in tests.
+        unsafe { self.to_bytes_with_nul().clone_to_uninit(dst as *mut [u8]) }
     }
 }
 
diff --git a/library/core/src/clone/uninit.rs b/library/core/src/clone/uninit.rs
new file mode 100644
index 00000000000..8b738bec796
--- /dev/null
+++ b/library/core/src/clone/uninit.rs
@@ -0,0 +1,128 @@
+use crate::mem::{self, MaybeUninit};
+use crate::ptr;
+
+/// Private specialization trait used by CloneToUninit, as per
+/// [the dev guide](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);
+        }
+    }
+}
diff --git a/library/core/tests/clone.rs b/library/core/tests/clone.rs
index b7130f16f87..71a328733b7 100644
--- a/library/core/tests/clone.rs
+++ b/library/core/tests/clone.rs
@@ -1,5 +1,7 @@
 use core::clone::CloneToUninit;
+use core::ffi::CStr;
 use core::mem::MaybeUninit;
+use core::ptr;
 
 #[test]
 #[allow(suspicious_double_ref_op)]
@@ -81,3 +83,41 @@ fn test_clone_to_uninit_slice_drops_on_panic() {
     drop(a);
     assert_eq!(COUNTER.load(Relaxed), 0);
 }
+
+#[test]
+fn test_clone_to_uninit_str() {
+    let a = "hello";
+
+    let mut storage: MaybeUninit<[u8; 5]> = MaybeUninit::uninit();
+    unsafe { a.clone_to_uninit(storage.as_mut_ptr() as *mut [u8] as *mut str) };
+    assert_eq!(a.as_bytes(), unsafe { storage.assume_init() }.as_slice());
+
+    let mut b: Box<str> = "world".into();
+    assert_eq!(a.len(), b.len());
+    assert_ne!(a, &*b);
+    unsafe { a.clone_to_uninit(ptr::from_mut::<str>(&mut b)) };
+    assert_eq!(a, &*b);
+}
+
+#[test]
+fn test_clone_to_uninit_cstr() {
+    let a = c"hello";
+
+    let mut storage: MaybeUninit<[u8; 6]> = MaybeUninit::uninit();
+    unsafe { a.clone_to_uninit(storage.as_mut_ptr() as *mut [u8] as *mut CStr) };
+    assert_eq!(a.to_bytes_with_nul(), unsafe { storage.assume_init() }.as_slice());
+
+    let mut b: Box<CStr> = c"world".into();
+    assert_eq!(a.count_bytes(), b.count_bytes());
+    assert_ne!(a, &*b);
+    unsafe { a.clone_to_uninit(ptr::from_mut::<CStr>(&mut b)) };
+    assert_eq!(a, &*b);
+}
+
+#[test]
+fn cstr_metadata_is_length_with_nul() {
+    let s: &CStr = c"abcdef";
+    let p: *const CStr = ptr::from_ref(s);
+    let bytes: *const [u8] = p as *const [u8];
+    assert_eq!(s.to_bytes_with_nul().len(), bytes.len());
+}
diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index a501bcc98cf..918eec2d0d8 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -3,10 +3,13 @@
 #[cfg(test)]
 mod tests;
 
+use core::clone::CloneToUninit;
+
 use crate::borrow::{Borrow, Cow};
 use crate::collections::TryReserveError;
 use crate::hash::{Hash, Hasher};
 use crate::ops::{self, Range};
+use crate::ptr::addr_of_mut;
 use crate::rc::Rc;
 use crate::str::FromStr;
 use crate::sync::Arc;
@@ -1261,6 +1264,16 @@ impl Clone for Box<OsStr> {
     }
 }
 
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for OsStr {
+    #[inline]
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: we're just a wrapper around a platform-specific Slice
+        unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+    }
+}
+
 #[stable(feature = "shared_from_slice2", since = "1.24.0")]
 impl From<OsString> for Arc<OsStr> {
     /// Converts an [`OsString`] into an <code>[Arc]<[OsStr]></code> by moving the [`OsString`]
diff --git a/library/std/src/ffi/os_str/tests.rs b/library/std/src/ffi/os_str/tests.rs
index 5b39b9e34d8..67147934b4d 100644
--- a/library/std/src/ffi/os_str/tests.rs
+++ b/library/std/src/ffi/os_str/tests.rs
@@ -1,4 +1,6 @@
 use super::*;
+use crate::mem::MaybeUninit;
+use crate::ptr;
 
 #[test]
 fn test_os_string_with_capacity() {
@@ -286,3 +288,18 @@ fn slice_surrogate_edge() {
     assert_eq!(post_crab.slice_encoded_bytes(..4), "🦀");
     assert_eq!(post_crab.slice_encoded_bytes(4..), surrogate);
 }
+
+#[test]
+fn clone_to_uninit() {
+    let a = OsStr::new("hello.txt");
+
+    let mut storage = vec![MaybeUninit::<u8>::uninit(); size_of_val::<OsStr>(a)];
+    unsafe { a.clone_to_uninit(ptr::from_mut::<[_]>(storage.as_mut_slice()) as *mut OsStr) };
+    assert_eq!(a.as_encoded_bytes(), unsafe { MaybeUninit::slice_assume_init_ref(&storage) });
+
+    let mut b: Box<OsStr> = OsStr::new("world.exe").into();
+    assert_eq!(size_of_val::<OsStr>(a), size_of_val::<OsStr>(&b));
+    assert_ne!(a, &*b);
+    unsafe { a.clone_to_uninit(ptr::from_mut::<OsStr>(&mut b)) };
+    assert_eq!(a, &*b);
+}
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 93a74ef739b..2530a376387 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -319,6 +319,7 @@
 // tidy-alphabetical-start
 #![feature(c_str_module)]
 #![feature(char_internals)]
+#![feature(clone_to_uninit)]
 #![feature(core_intrinsics)]
 #![feature(core_io_borrowed_buf)]
 #![feature(duration_constants)]
diff --git a/library/std/src/path.rs b/library/std/src/path.rs
index 80163667636..9eaa0e01c2c 100644
--- a/library/std/src/path.rs
+++ b/library/std/src/path.rs
@@ -70,6 +70,8 @@
 #[cfg(test)]
 mod tests;
 
+use core::clone::CloneToUninit;
+
 use crate::borrow::{Borrow, Cow};
 use crate::collections::TryReserveError;
 use crate::error::Error;
@@ -3109,6 +3111,16 @@ impl Path {
     }
 }
 
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Path {
+    #[inline]
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: Path is just a wrapper around OsStr
+        unsafe { self.inner.clone_to_uninit(core::ptr::addr_of_mut!((*dst).inner)) }
+    }
+}
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl AsRef<OsStr> for Path {
     #[inline]
diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs
index a12e42cba0c..6436872087d 100644
--- a/library/std/src/path/tests.rs
+++ b/library/std/src/path/tests.rs
@@ -3,6 +3,8 @@ use core::hint::black_box;
 use super::*;
 use crate::collections::{BTreeSet, HashSet};
 use crate::hash::DefaultHasher;
+use crate::mem::MaybeUninit;
+use crate::ptr;
 
 #[allow(unknown_lints, unused_macro_rules)]
 macro_rules! t (
@@ -2054,3 +2056,20 @@ fn bench_hash_path_long(b: &mut test::Bencher) {
 
     black_box(hasher.finish());
 }
+
+#[test]
+fn clone_to_uninit() {
+    let a = Path::new("hello.txt");
+
+    let mut storage = vec![MaybeUninit::<u8>::uninit(); size_of_val::<Path>(a)];
+    unsafe { a.clone_to_uninit(ptr::from_mut::<[_]>(storage.as_mut_slice()) as *mut Path) };
+    assert_eq!(a.as_os_str().as_encoded_bytes(), unsafe {
+        MaybeUninit::slice_assume_init_ref(&storage)
+    });
+
+    let mut b: Box<Path> = Path::new("world.exe").into();
+    assert_eq!(size_of_val::<Path>(a), size_of_val::<Path>(&b));
+    assert_ne!(a, &*b);
+    unsafe { a.clone_to_uninit(ptr::from_mut::<Path>(&mut b)) };
+    assert_eq!(a, &*b);
+}
diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs
index 0f8bd645352..992767211d0 100644
--- a/library/std/src/sys/os_str/bytes.rs
+++ b/library/std/src/sys/os_str/bytes.rs
@@ -1,6 +1,9 @@
 //! The underlying OsString/OsStr implementation on Unix and many other
 //! systems: just a `Vec<u8>`/`[u8]`.
 
+use core::clone::CloneToUninit;
+use core::ptr::addr_of_mut;
+
 use crate::borrow::Cow;
 use crate::collections::TryReserveError;
 use crate::fmt::Write;
@@ -345,3 +348,13 @@ impl Slice {
         self.inner.eq_ignore_ascii_case(&other.inner)
     }
 }
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Slice {
+    #[inline]
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: we're just a wrapper around [u8]
+        unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+    }
+}
diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs
index ed975ba58b5..433237aa6e7 100644
--- a/library/std/src/sys/os_str/wtf8.rs
+++ b/library/std/src/sys/os_str/wtf8.rs
@@ -1,5 +1,8 @@
 //! The underlying OsString/OsStr implementation on Windows is a
 //! wrapper around the "WTF-8" encoding; see the `wtf8` module for more.
+use core::clone::CloneToUninit;
+use core::ptr::addr_of_mut;
+
 use crate::borrow::Cow;
 use crate::collections::TryReserveError;
 use crate::rc::Rc;
@@ -268,3 +271,13 @@ impl Slice {
         self.inner.eq_ignore_ascii_case(&other.inner)
     }
 }
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Slice {
+    #[inline]
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: we're just a wrapper around Wtf8
+        unsafe { self.inner.clone_to_uninit(addr_of_mut!((*dst).inner)) }
+    }
+}
diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs
index 277c9506feb..063451ad54e 100644
--- a/library/std/src/sys_common/wtf8.rs
+++ b/library/std/src/sys_common/wtf8.rs
@@ -19,12 +19,14 @@
 mod tests;
 
 use core::char::{encode_utf16_raw, encode_utf8_raw};
+use core::clone::CloneToUninit;
 use core::str::next_code_point;
 
 use crate::borrow::Cow;
 use crate::collections::TryReserveError;
 use crate::hash::{Hash, Hasher};
 use crate::iter::FusedIterator;
+use crate::ptr::addr_of_mut;
 use crate::rc::Rc;
 use crate::sync::Arc;
 use crate::sys_common::AsInner;
@@ -1046,3 +1048,13 @@ impl Hash for Wtf8 {
         0xfeu8.hash(state)
     }
 }
+
+#[unstable(feature = "clone_to_uninit", issue = "126799")]
+unsafe impl CloneToUninit for Wtf8 {
+    #[inline]
+    #[cfg_attr(debug_assertions, track_caller)]
+    unsafe fn clone_to_uninit(&self, dst: *mut Self) {
+        // SAFETY: we're just a wrapper around [u8]
+        unsafe { self.bytes.clone_to_uninit(addr_of_mut!((*dst).bytes)) }
+    }
+}