From 2e08c7decbe2c13916d4958ee158e3628954d0f2 Mon Sep 17 00:00:00 2001 From: Genki Sato <13384803+gksato@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:53:13 +0900 Subject: [PATCH 1/3] Doc: `MaybeUninit::assume_init_read` Safety: warn on thread safety Due to `impl Sync for MaybeUninit`, `MaybeUninit::assume_init_read` is prone to the illegal use transferring ownership of `!Send + Sync` value across threads. This commit adds a warning on this thread safety problem to the method's Safety section. This commit also adds two code examples related to this problem. --- library/core/src/mem/maybe_uninit.rs | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs index 94703940baa1f..7b4c603d95927 100644 --- a/library/core/src/mem/maybe_uninit.rs +++ b/library/core/src/mem/maybe_uninit.rs @@ -750,8 +750,22 @@ impl MaybeUninit { /// `assume_init_read` and then [`assume_init`]), it is your responsibility /// to ensure that data may indeed be duplicated. /// + /// Additionally, if you call this function from a different thread than the one + /// that holds the `MaybeUninit`, it logically constitutes a cross-thread ownership + /// transfer of the contained value `T`. You are responsible for guaranteeing + /// the thread safety of the transfer. Note that `MaybeUninit` is [`Sync`] if `T` + /// is merely [`Sync`]; therefore, safely obtaining `&MaybeUninit` does *not* ensure + /// the said thread safety. If `T` is [`Send`], the thread safety is + /// guaranteed; otherwise, you will need to provide type-specific reasoning to + /// prove that the transfer and your usage of the value `T` are actually thread-safe. + /// If your type's safe API relies on this function, you may need to manually + /// implement `!Send`, `!Sync`, [`Send`], and/or [`Sync`] to *tighten* the conditions + /// for the type to be [`Send`] and/or [`Sync`]. + /// /// [inv]: #initialization-invariant /// [`assume_init`]: MaybeUninit::assume_init + /// [`Send`]: crate::marker::Send + /// [`Sync`]: crate::marker::Sync /// /// # Examples /// @@ -759,6 +773,7 @@ impl MaybeUninit { /// /// ```rust /// use std::mem::MaybeUninit; + /// use std::thread; /// /// let mut x = MaybeUninit::::uninit(); /// x.write(13); @@ -773,12 +788,25 @@ impl MaybeUninit { /// // Duplicating a `None` value is okay, so we may read multiple times. /// let x2 = unsafe { x.assume_init_read() }; /// assert_eq!(x1, x2); + /// + /// let mut x = MaybeUninit::>::uninit(); + /// x.write(vec![0, 1, 2]); + /// // Moving the vector to another thread. We may read only once. + /// // `Vec` is `Send`, so this is thread-safe. + /// thread::scope(|s| { + /// s.spawn(|| { + /// let v = unsafe { x.assume_init_read() }; + /// assert_eq!(v, vec![0, 1, 2]); + /// }); + /// }); /// ``` /// /// *Incorrect* usage of this method: /// /// ```rust,no_run /// use std::mem::MaybeUninit; + /// use std::thread; + /// use std::sync::Mutex; /// /// let mut x = MaybeUninit::>>::uninit(); /// x.write(Some(vec![0, 1, 2])); @@ -786,6 +814,17 @@ impl MaybeUninit { /// let x2 = unsafe { x.assume_init_read() }; /// // We now created two copies of the same vector, leading to a double-free ⚠️ when /// // they both get dropped! + /// + /// let mtx = Mutex::new(0u32); + /// let x = MaybeUninit::new(mtx.lock().unwrap()); + /// // Moving the `MutexGuard<'_, u32>: !Send + Sync` to another thread. + /// // ⚠️ Thread safety is not guaranteed here! + /// thread::scope(|s| { + /// // This compiles because `MaybeUninit>` is `Sync`. + /// s.spawn(|| { + /// let _unused = unsafe { x.assume_init_read() }; + /// }); + /// }); /// ``` #[stable(feature = "maybe_uninit_extra", since = "1.60.0")] #[rustc_const_stable(feature = "const_maybe_uninit_assume_init_read", since = "1.75.0")] From f53a45699841b6c74631be5da701a235752c74ee Mon Sep 17 00:00:00 2001 From: Genki Sato <13384803+gksato@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:44:12 +0900 Subject: [PATCH 2/3] Reword `MaybeUninit::assume_init_read` doc * Add a introductory sentence to the thread safety paragraph in Safety section * Clarify that a bad usage example is actually library UB --- library/core/src/mem/maybe_uninit.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs index 7b4c603d95927..4c61d40c66680 100644 --- a/library/core/src/mem/maybe_uninit.rs +++ b/library/core/src/mem/maybe_uninit.rs @@ -750,7 +750,8 @@ impl MaybeUninit { /// `assume_init_read` and then [`assume_init`]), it is your responsibility /// to ensure that data may indeed be duplicated. /// - /// Additionally, if you call this function from a different thread than the one + /// Since this function does not require `T: Send`, you also need to carefully consider + /// thread safety. If you call this function from a different thread than the one /// that holds the `MaybeUninit`, it logically constitutes a cross-thread ownership /// transfer of the contained value `T`. You are responsible for guaranteeing /// the thread safety of the transfer. Note that `MaybeUninit` is [`Sync`] if `T` @@ -817,12 +818,14 @@ impl MaybeUninit { /// /// let mtx = Mutex::new(0u32); /// let x = MaybeUninit::new(mtx.lock().unwrap()); - /// // Moving the `MutexGuard<'_, u32>: !Send + Sync` to another thread. - /// // ⚠️ Thread safety is not guaranteed here! + /// // Moving the `MutexGuard<'_, u32>: !Send + Sync` to another thread, + /// // thread safety not guaranteed here. /// thread::scope(|s| { /// // This compiles because `MaybeUninit>` is `Sync`. /// s.spawn(|| { /// let _unused = unsafe { x.assume_init_read() }; + /// // `_unused: MutexGuard<'_, u32>` is dropped here, on a different thread + /// // than the one that locked the mutex; this is library-level UB ⚠️! /// }); /// }); /// ``` From e99dc9bc305bdbcd394b5a4f039856ff507b6db4 Mon Sep 17 00:00:00 2001 From: Genki Sato <13384803+gksato@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:28:41 +0900 Subject: [PATCH 3/3] Reword thread-safety section of MaybeUninit::assume_init_read Removed reference on Wrappers to keep it concise, and added irrelevance of `Sync`. --- library/core/src/mem/maybe_uninit.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs index 4c61d40c66680..9d39213d7c6eb 100644 --- a/library/core/src/mem/maybe_uninit.rs +++ b/library/core/src/mem/maybe_uninit.rs @@ -750,18 +750,18 @@ impl MaybeUninit { /// `assume_init_read` and then [`assume_init`]), it is your responsibility /// to ensure that data may indeed be duplicated. /// - /// Since this function does not require `T: Send`, you also need to carefully consider - /// thread safety. If you call this function from a different thread than the one - /// that holds the `MaybeUninit`, it logically constitutes a cross-thread ownership - /// transfer of the contained value `T`. You are responsible for guaranteeing - /// the thread safety of the transfer. Note that `MaybeUninit` is [`Sync`] if `T` - /// is merely [`Sync`]; therefore, safely obtaining `&MaybeUninit` does *not* ensure - /// the said thread safety. If `T` is [`Send`], the thread safety is - /// guaranteed; otherwise, you will need to provide type-specific reasoning to - /// prove that the transfer and your usage of the value `T` are actually thread-safe. - /// If your type's safe API relies on this function, you may need to manually - /// implement `!Send`, `!Sync`, [`Send`], and/or [`Sync`] to *tighten* the conditions - /// for the type to be [`Send`] and/or [`Sync`]. + /// As calling this function does not require `T: Send`, you also need to manually + /// ensure thread safety. Since `MaybeUninit` is [`Sync`] whenever `T` is [`Sync`], + /// a reference to `MaybeUninit` can be sent across threads without requiring + /// that `T` is [`Send`]. Calling this method through the reference, now in another thread, + /// effectively constitutes a cross-thread transfer of the value, whose type is possibly + /// non-[`Send`]. Callers using this function in this cross-thread way must ensure + /// that the transfer of the return value and its subsequent use or drop are thread-safe. + /// In particular, if a safe abstraction lets this function read generic `T` into another + /// thread, the abstraction is required to satisfy a generic condition that ensures the + /// soundness of the cross-thread transfer: most simply, `T: Send`; or as a substitute, + /// `T: Sync + Copy`. As you can see, despite `impl Sync for MaybeUninit`, + /// `T: Sync` is not primarily relevant to the thread safety of this method. /// /// [inv]: #initialization-invariant /// [`assume_init`]: MaybeUninit::assume_init