forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of rust-lang#97629 - guswynn:exclusive_struct, r=m-ou-se
[core] add `Exclusive` to sync (discussed here: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Adding.20.60SyncWrapper.60.20to.20std) `Exclusive` is a wrapper that exclusively allows mutable access to the inner value if you have exclusive access to the wrapper. It acts like a compile time mutex, and hold an unconditional `Sync` implementation. ## Justification for inclusion into std - This wrapper unblocks actual problems: - The example that I hit was a vector of `futures::future::BoxFuture`'s causing a central struct in a script to be non-`Sync`. To work around it, you either write really difficult code, or wrap the futures in a needless mutex. - Easy to maintain: this struct is as simple as a wrapper can get, and its `Sync` implementation has very clear reasoning - Fills a gap: `&/&mut` are to `RwLock` as `Exclusive` is to `Mutex` ## Public Api ```rust // core::sync #[derive(Default)] struct Exclusive<T: ?Sized> { ... } impl<T: ?Sized> Sync for Exclusive {} impl<T> Exclusive<T> { pub const fn new(t: T) -> Self; pub const fn into_inner(self) -> T; } impl<T: ?Sized> Exclusive<T> { pub const fn get_mut(&mut self) -> &mut T; pub const fn get_pin_mut(Pin<&mut self>) -> Pin<&mut T>; pub const fn from_mut(&mut T) -> &mut Exclusive<T>; pub const fn from_pin_mut(Pin<&mut T>) -> Pin<&mut Exclusive<T>>; } impl<T: Future> Future for Exclusive { ... } impl<T> From<T> for Exclusive<T> { ... } impl<T: ?Sized> Debug for Exclusive { ... } ``` ## Naming This is a big bikeshed, but I felt that `Exclusive` captured its general purpose quite well. ## Stability and location As this is so simple, it can be in `core`. I feel that it can be stabilized quite soon after it is merged, if the libs teams feels its reasonable to add. Also, I don't really know how unstable feature work in std/core's codebases, so I might need help fixing them ## Tips for review The docs probably are the thing that needs to be reviewed! I tried my best, but I'm sure people have more experience than me writing docs for `Core` ### Implementation: The API is mostly pulled from https://docs.rs/sync_wrapper/latest/sync_wrapper/struct.SyncWrapper.html (which is apache 2.0 licenesed), and the implementation is trivial: - its an unsafe justification for pinning - its an unsafe justification for the `Sync` impl (mostly reasoned about by ```@danielhenrymantilla``` here: Actyx/sync_wrapper#2) - and forwarding impls, starting with derivable ones and `Future`
- Loading branch information
Showing
4 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
//! Defines [`Exclusive`]. | ||
|
||
use core::fmt; | ||
use core::future::Future; | ||
use core::pin::Pin; | ||
use core::task::{Context, Poll}; | ||
|
||
/// `Exclusive` provides only _mutable_ access, also referred to as _exclusive_ | ||
/// access to the underlying value. It provides no _immutable_, or _shared_ | ||
/// access to the underlying value. | ||
/// | ||
/// While this may seem not very useful, it allows `Exclusive` to _unconditionally_ | ||
/// implement [`Sync`]. Indeed, the safety requirements of `Sync` state that for `Exclusive` | ||
/// to be `Sync`, it must be sound to _share_ across threads, that is, it must be sound | ||
/// for `&Exclusive` to cross thread boundaries. By design, a `&Exclusive` has no API | ||
/// whatsoever, making it useless, thus harmless, thus memory safe. | ||
/// | ||
/// Certain constructs like [`Future`]s can only be used with _exclusive_ access, | ||
/// and are often `Send` but not `Sync`, so `Exclusive` can be used as hint to the | ||
/// rust compiler that something is `Sync` in practice. | ||
/// | ||
/// ## Examples | ||
/// Using a non-`Sync` future prevents the wrapping struct from being `Sync` | ||
/// ```compile_fail | ||
/// use core::cell::Cell; | ||
/// | ||
/// async fn other() {} | ||
/// fn assert_sync<T: Sync>(t: T) {} | ||
/// struct State<F> { | ||
/// future: F | ||
/// } | ||
/// | ||
/// assert_sync(State { | ||
/// future: async { | ||
/// let cell = Cell::new(1); | ||
/// let cell_ref = &cell; | ||
/// other().await; | ||
/// let value = cell_ref.get(); | ||
/// } | ||
/// }); | ||
/// ``` | ||
/// | ||
/// `Exclusive` ensures the struct is `Sync` without stripping the future of its | ||
/// functionality. | ||
/// ``` | ||
/// #![feature(exclusive_wrapper)] | ||
/// use core::cell::Cell; | ||
/// use core::sync::Exclusive; | ||
/// | ||
/// async fn other() {} | ||
/// fn assert_sync<T: Sync>(t: T) {} | ||
/// struct State<F> { | ||
/// future: Exclusive<F> | ||
/// } | ||
/// | ||
/// assert_sync(State { | ||
/// future: Exclusive::new(async { | ||
/// let cell = Cell::new(1); | ||
/// let cell_ref = &cell; | ||
/// other().await; | ||
/// let value = cell_ref.get(); | ||
/// }) | ||
/// }); | ||
/// ``` | ||
/// | ||
/// ## Parallels with a mutex | ||
/// In some sense, `Exclusive` can be thought of as a _compile-time_ version of | ||
/// a mutex, as the borrow-checker guarantees that only one `&mut` can exist | ||
/// for any value. This is a parallel with the fact that | ||
/// `&` and `&mut` references together can be thought of as a _compile-time_ | ||
/// version of a read-write lock. | ||
/// | ||
/// | ||
/// [`Sync`]: core::marker::Sync | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[doc(alias = "SyncWrapper")] | ||
#[doc(alias = "SyncCell")] | ||
#[doc(alias = "Unique")] | ||
// `Exclusive` can't have `PartialOrd`, `Clone`, etc. impls as they would | ||
// use `&` access to the inner value, violating the `Sync` impl's safety | ||
// requirements. | ||
#[derive(Default)] | ||
#[repr(transparent)] | ||
pub struct Exclusive<T: ?Sized> { | ||
inner: T, | ||
} | ||
|
||
// See `Exclusive`'s docs for justification. | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
unsafe impl<T: ?Sized> Sync for Exclusive<T> {} | ||
|
||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
impl<T: ?Sized> fmt::Debug for Exclusive<T> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { | ||
f.debug_struct("Exclusive").finish_non_exhaustive() | ||
} | ||
} | ||
|
||
impl<T: Sized> Exclusive<T> { | ||
/// Wrap a value in an `Exclusive` | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn new(t: T) -> Self { | ||
Self { inner: t } | ||
} | ||
|
||
/// Unwrap the value contained in the `Exclusive` | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn into_inner(self) -> T { | ||
self.inner | ||
} | ||
} | ||
|
||
impl<T: ?Sized> Exclusive<T> { | ||
/// Get exclusive access to the underlying value. | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn get_mut(&mut self) -> &mut T { | ||
&mut self.inner | ||
} | ||
|
||
/// Get pinned exclusive access to the underlying value. | ||
/// | ||
/// `Exclusive` is considered to _structurally pin_ the underlying | ||
/// value, which means _unpinned_ `Exclusive`s can produce _unpinned_ | ||
/// access to the underlying value, but _pinned_ `Exclusive`s only | ||
/// produce _pinned_ access to the underlying value. | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> { | ||
// SAFETY: `Exclusive` can only produce `&mut T` if itself is unpinned | ||
// `Pin::map_unchecked_mut` is not const, so we do this conversion manually | ||
unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().inner) } | ||
} | ||
|
||
/// Build a _mutable_ references to an `Exclusive<T>` from | ||
/// a _mutable_ reference to a `T`. This allows you to skip | ||
/// building an `Exclusive` with [`Exclusive::new`]. | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn from_mut(r: &'_ mut T) -> &'_ mut Exclusive<T> { | ||
// SAFETY: repr is ≥ C, so refs have the same layout; and `Exclusive` properties are `&mut`-agnostic | ||
unsafe { &mut *(r as *mut T as *mut Exclusive<T>) } | ||
} | ||
|
||
/// Build a _pinned mutable_ references to an `Exclusive<T>` from | ||
/// a _pinned mutable_ reference to a `T`. This allows you to skip | ||
/// building an `Exclusive` with [`Exclusive::new`]. | ||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
#[must_use] | ||
pub const fn from_pin_mut(r: Pin<&'_ mut T>) -> Pin<&'_ mut Exclusive<T>> { | ||
// SAFETY: `Exclusive` can only produce `&mut T` if itself is unpinned | ||
// `Pin::map_unchecked_mut` is not const, so we do this conversion manually | ||
unsafe { Pin::new_unchecked(Self::from_mut(r.get_unchecked_mut())) } | ||
} | ||
} | ||
|
||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
impl<T> From<T> for Exclusive<T> { | ||
fn from(t: T) -> Self { | ||
Self::new(t) | ||
} | ||
} | ||
|
||
#[unstable(feature = "exclusive_wrapper", issue = "98407")] | ||
impl<T: Future + ?Sized> Future for Exclusive<T> { | ||
type Output = T::Output; | ||
|
||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||
self.get_pin_mut().poll(cx) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters