Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ language_item_table! {
IndexMut, sym::index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1);

UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None;
CovariantUnsafeCell, sym::covariant_unsafe_cell, covariant_unsafe_cell_type, Target::Struct, GenericRequirement::Exact(1);
UnsafePinned, sym::unsafe_pinned, unsafe_pinned_type, Target::Struct, GenericRequirement::None;

VaArgSafe, sym::va_arg_safe, va_arg_safe, Target::Trait, GenericRequirement::None;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/variance/terms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn lang_items(tcx: TyCtxt<'_>) -> Vec<(LocalDefId, Vec<ty::Variance>)> {
let all = [
(lang_items.phantom_data(), vec![ty::Covariant]),
(lang_items.unsafe_cell_type(), vec![ty::Invariant]),
(lang_items.covariant_unsafe_cell_type(), vec![ty::Covariant]),
];

all.into_iter() // iterating over (Option<DefId>, Variance)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ symbols! {
cosf64,
cosf128,
count,
covariant_unsafe_cell,
coverage,
coverage_attribute,
cr,
Expand Down
3 changes: 3 additions & 0 deletions library/core/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,12 @@ use crate::pin::PinCoerceUnsized;
use crate::ptr::{self, NonNull};
use crate::range;

mod covariant_unsafe_cell;
mod lazy;
mod once;

#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
pub use covariant_unsafe_cell::CovariantUnsafeCell;
#[stable(feature = "lazy_cell", since = "1.80.0")]
pub use lazy::LazyCell;
#[stable(feature = "once_cell", since = "1.70.0")]
Expand Down
184 changes: 184 additions & 0 deletions library/core/src/cell/covariant_unsafe_cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use crate::cell::UnsafeCell;
use crate::fmt;
use crate::ops::CoerceUnsized;
use crate::ptr::{self, NonNull};

/// **Co**variant version of [`UnsafeCell`].
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[repr(transparent)]
#[rustc_pub_transparent]
// Implementation note:
//
// We could make `CovariantUnsafeCell` be the canonical lang item and make `UnsafeCell` a wrapper
// over it, with `PhantomData<*mut T>`. That would however be a huge compiler change, without clear
// benefit.
//
// As such, `CovariantUnsafeCell` is wrapping `UnsafeCell` instead. It is a lang-item only to
// hardcode its variance to be **co**variant in `T`, even though it is wrapping `UnsafeCell` which
// is **in**variant in `T`.
#[lang = "covariant_unsafe_cell"]
pub struct CovariantUnsafeCell<T: ?Sized>(UnsafeCell<T>);

#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
impl<T: ?Sized> !Sync for CovariantUnsafeCell<T> {}

impl<T> CovariantUnsafeCell<T> {
/// Constructs a new instance of `CovariantUnsafeCell` which will wrap the specified value.
///
/// All access to the inner value through `&CovariantUnsafeCell<T>` requires `unsafe` code.
///
/// # Examples
///
/// ```
/// #![feature(covariant_unsafe_cell)]
/// use std::cell::CovariantUnsafeCell;
///
/// let uc = CovariantUnsafeCell::new(5);
/// ```
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_stable_indirect]
#[inline(always)]
pub const fn new(value: T) -> CovariantUnsafeCell<T> {
CovariantUnsafeCell(UnsafeCell::new(value))
}

/// Unwraps the value, consuming the cell.
///
/// # Examples
///
/// ```
/// #![feature(covariant_unsafe_cell)]
/// use std::cell::CovariantUnsafeCell;
///
/// let uc = CovariantUnsafeCell::new(5);
///
/// let five = uc.into_inner();
/// ```
#[inline(always)]
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")]
pub const fn into_inner(self) -> T {
self.0.into_inner()
}
}

impl<T: ?Sized> CovariantUnsafeCell<T> {
/// Gets a mutable non-null pointer to the wrapped value.
///
/// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you
/// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion
/// and caveats.
///
/// [the `UnsafeCell` type-level docs]: super::UnsafeCell#aliasing-rules
///
/// # Examples
///
/// ```
/// #![feature(covariant_unsafe_cell)]
/// use std::cell::CovariantUnsafeCell;
/// use std::ptr::NonNull;
///
/// let uc = CovariantUnsafeCell::new(5);
///
/// let ptr: NonNull<i32> = uc.get();
/// ```
#[inline(always)]
#[rustc_as_ptr]
#[rustc_should_not_be_called_on_const_items]
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")]
pub const fn get(&self) -> NonNull<T> {
// We can just cast the pointer from `CovariantUnsafeCell<T>` to `T` because of
// #[repr(transparent)].
//
// Note that this is also known to be allowed for user code as per
// `#[rustc_pub_transparent]`.
// SAFETY: the pointer is not null, as it comes from a reference
unsafe { NonNull::new_unchecked(ptr::from_ref(self).cast_mut() as *mut T) }
}

/// Returns a mutable reference to the underlying data.
///
/// This call borrows the `CovariantUnsafeCell` mutably (at compile-time) which guarantees that
/// we possess the only reference.
///
/// # Examples
///
/// ```
/// #![feature(covariant_unsafe_cell)]
/// use std::cell::CovariantUnsafeCell;
///
/// let mut c = CovariantUnsafeCell::new(5);
/// *c.get_mut() += 1;
///
/// assert_eq!(*c.get_mut(), 6);
/// ```
#[inline(always)]
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")]
pub const fn get_mut(&mut self) -> &mut T {
self.0.get_mut()
}

/// Gets a mutable pointer to the wrapped value.
/// The difference from [`get`] is that this function accepts a raw pointer,
/// which is useful to avoid the creation of temporary references.
///
/// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you
/// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion
/// and caveats.
///
/// [`get`]: CovariantUnsafeCell::get()
///
/// # Examples
///
/// Gradual initialization of an `CovariantUnsafeCell` requires `raw_get`, as
/// calling `get` would require creating a reference to uninitialized data:
///
/// ```
/// #![feature(covariant_unsafe_cell)]
/// use std::cell::CovariantUnsafeCell;
/// use std::mem::MaybeUninit;
///
/// let m = MaybeUninit::<CovariantUnsafeCell<i32>>::uninit();
/// unsafe { CovariantUnsafeCell::raw_get(m.as_ptr()).write(5); }
/// // avoid below which references to uninitialized data
/// // unsafe { CovariantUnsafeCell::get(&*m.as_ptr()).write(5); }
/// let uc = unsafe { m.assume_init() };
///
/// assert_eq!(uc.into_inner(), 5);
/// ```
#[inline(always)]
#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
#[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")]
pub const fn raw_get(this: *const Self) -> *mut T {
// We can just cast the pointer from `UnsafeCell<T>` to `T` because of
// #[repr(transparent)].
//
// Note that this is also known to be allowed for user code as per
// `#[rustc_pub_transparent]`.
this as *const T as *mut T
}
}

#[unstable(feature = "coerce_unsized", issue = "18598")]
impl<T: CoerceUnsized<U>, U> CoerceUnsized<CovariantUnsafeCell<U>> for CovariantUnsafeCell<T> {}

#[unstable(feature = "covariant_unsafe_cell", issue = "159735")]
impl<T: ?Sized> fmt::Debug for CovariantUnsafeCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CovariantUnsafeCell").finish_non_exhaustive()
}
}

#[cfg(test)]
mod tests {
use super::*;

fn _covarience<'short, 'long: 'short>(
x: CovariantUnsafeCell<&'long ()>,
) -> CovariantUnsafeCell<&'short ()> {
x
}
}
59 changes: 48 additions & 11 deletions library/core/src/cell/lazy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::UnsafeCell;
use crate::cell::CovariantUnsafeCell;
use crate::hint::unreachable_unchecked;
use crate::ops::{Deref, DerefMut};
use crate::{fmt, mem};
Expand Down Expand Up @@ -52,7 +52,44 @@ enum State<T, F> {
/// ```
#[stable(feature = "lazy_cell", since = "1.80.0")]
pub struct LazyCell<T, F = fn() -> T> {
state: UnsafeCell<State<T, F>>,
// It is non-obvious why `LazyCell` can be covariant in both `T` and `F`
// (and thus use `CovariantUnsafeCell`)...
//
// # `F`
//
// `F` is the easier to explain one; The state only ever transitions *out* of `Uninit(F)`
// (to either `Poisoned` or `Init(T)`), and never into it. In other words `F` is only ever
// read, not written.
//
// One could imagine `LazyCell` implemented as
// ```
// struct {
// state: UnsafeCell<Uninit | Init(T) | Poisoned>,
// f: ManuallyDrop<F>,
// }
// ```
// which would make it "obviously" covariant in `F`.
//
// **NOTE**: the important invariant here is that we never allow writing `F` through
// `&LazyCell`.
//
// # `T`
//
// Why `LazyCell` can be covariant in `T` is even more subtle. We do allow writing `T` through
// a `&LazyCell`, which would normally force us to make `LazyCell` invariant in `T`. However,
// the only value that we allow writing is one returned by `F`... and we don't allow changing
// `F`. So even if a user gets a `&LazyCell<LessrestrictedVersionOfT>`, they cannot write the
// `LessrestrictedVersionOfT` through it.
//
// **NOTE**: the important invariants here are that
// 1. `T` can only be written through `&LazyCell<T, F>` by being returned from `F`
// 2. `F` cannot be overwritten after `LazyCell` creation
//
// # Conclusion
//
// `LazyCell` can be covariant in both `T` and `F`... provided the non-local invariants which
// are listed above.
state: CovariantUnsafeCell<State<T, F>>,
}

impl<T, F: FnOnce() -> T> LazyCell<T, F> {
Expand All @@ -73,7 +110,7 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
#[stable(feature = "lazy_cell", since = "1.80.0")]
#[rustc_const_stable(feature = "lazy_cell", since = "1.80.0")]
pub const fn new(f: F) -> LazyCell<T, F> {
LazyCell { state: UnsafeCell::new(State::Uninit(f)) }
LazyCell { state: CovariantUnsafeCell::new(State::Uninit(f)) }
}

/// Consumes this `LazyCell` returning the stored value.
Expand Down Expand Up @@ -141,7 +178,7 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
// reference lives either until the end of the borrow of `this` (in the
// initialized case) or is invalidated in `really_init` (in the
// uninitialized case; `really_init` will create and return a fresh reference).
let state = unsafe { &*this.state.get() };
let state = unsafe { this.state.get().as_ref() };
match state {
State::Init(data) => data,
// SAFETY: The state is uninitialized.
Expand Down Expand Up @@ -231,7 +268,7 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
// This function is only called when the state is uninitialized,
// so no references to `state` can exist except for the reference
// in `force`, which is invalidated here and not accessed again.
let state = unsafe { &mut *this.state.get() };
let state = unsafe { this.state.get().as_mut() };
// Temporarily mark the state as poisoned. This prevents reentrant
// accesses and correctly poisons the cell if the closure panicked.
let State::Uninit(f) = mem::replace(state, State::Poisoned) else { unreachable!() };
Expand All @@ -242,15 +279,15 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
// If the closure accessed the cell through something like a reentrant
// mutex, but caught the panic resulting from the state being poisoned,
// the mutable borrow for `state` will be invalidated, so we need to
// go through the `UnsafeCell` pointer here. The state can only be
// poisoned at this point, so using `write` to skip the destructor
// of `State` should help the optimizer.
// go through the `CovariantUnsafeCell` pointer here. The state can
// only be poisoned at this point, so using `write` to skip the
// destructor of `State` should help the optimizer.
unsafe { this.state.get().write(State::Init(data)) };

// SAFETY:
// The previous references were invalidated by the `write` call above,
// so do a new shared borrow of the state instead.
let state = unsafe { &*this.state.get() };
let state = unsafe { this.state.get().as_ref() };
let State::Init(data) = state else { unreachable!() };
data
}
Expand Down Expand Up @@ -303,7 +340,7 @@ impl<T, F> LazyCell<T, F> {
// This is sound for the same reason as in `force`: once the state is
// initialized, it will not be mutably accessed again, so this reference
// will stay valid for the duration of the borrow to `self`.
let state = unsafe { &*this.state.get() };
let state = unsafe { this.state.get().as_ref() };
match state {
State::Init(data) => Some(data),
_ => None,
Expand Down Expand Up @@ -373,7 +410,7 @@ impl<T, F> From<T> for LazyCell<T, F> {
/// with the provided value.
#[inline]
fn from(value: T) -> Self {
Self { state: UnsafeCell::new(State::Init(value)) }
Self { state: CovariantUnsafeCell::new(State::Init(value)) }
}
}

Expand Down
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
#![feature(cfg_target_thread_local)]
#![feature(cfi_encoding)]
#![feature(const_trait_impl)]
#![feature(covariant_unsafe_cell)]
#![feature(decl_macro)]
#![feature(deprecated_suggestion)]
#![feature(diagnostic_on_move)]
Expand Down
Loading
Loading