Skip to content

Commit

Permalink
Add once_cell::race::OnceRef
Browse files Browse the repository at this point in the history
Like OnceBox, but stores a shared reference instead of a Box.
  • Loading branch information
gh2o committed Dec 29, 2022
1 parent 0d0dae1 commit fae9f73
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

-

## 1.17.0

- Add `race::OnceRef` for storing a `&'a T`.

## 1.16.0

- Add `no_std` implementation based on `critical-section`,
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "once_cell"
version = "1.16.0"
version = "1.17.0"
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
Expand Down
92 changes: 92 additions & 0 deletions src/race.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use atomic_polyfill as atomic;
use core::sync::atomic;

use atomic::{AtomicUsize, Ordering};
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::num::NonZeroUsize;

/// A thread-safe cell which can be written to only once.
Expand Down Expand Up @@ -172,6 +174,96 @@ impl OnceBool {
}
}

/// A thread-safe cell which can be written to only once.
pub struct OnceRef<'a, T> {
inner: OnceNonZeroUsize,
ghost: PhantomData<UnsafeCell<&'a T>>,
}

// TODO: Replace UnsafeCell with SyncUnsafeCell once stabilized
unsafe impl<'a, T: Sync> Sync for OnceRef<'a, T> {}

impl<'a, T> core::fmt::Debug for OnceRef<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "OnceRef({:?})", self.inner)
}
}

impl<'a, T> Default for OnceRef<'a, T> {
fn default() -> Self {
Self::new()
}
}

impl<'a, T> OnceRef<'a, T> {
/// Creates a new empty cell.
pub const fn new() -> OnceRef<'a, T> {
OnceRef { inner: OnceNonZeroUsize::new(), ghost: PhantomData }
}

/// Gets a reference to the underlying value.
pub fn get(&self) -> Option<&'a T> {
self.inner.get().map(|ptr| unsafe { &*(ptr.get() as *const T) })
}

/// Sets the contents of this cell to `value`.
///
/// Returns `Ok(())` if the cell was empty and `Err(value)` if it was
/// full.
pub fn set(&self, value: &'a T) -> Result<(), ()> {
let ptr = NonZeroUsize::new(value as *const T as usize).unwrap();
self.inner.set(ptr)
}

/// Gets the contents of the cell, initializing it with `f` if the cell was
/// empty.
///
/// If several threads concurrently run `get_or_init`, more than one `f` can
/// be called. However, all threads will return the same value, produced by
/// some `f`.
pub fn get_or_init<F>(&self, f: F) -> &'a T
where
F: FnOnce() -> &'a T,
{
let f = || NonZeroUsize::new(f() as *const T as usize).unwrap();
let ptr = self.inner.get_or_init(f);
unsafe { &*(ptr.get() as *const T) }
}

/// Gets the contents of the cell, initializing it with `f` if
/// the cell was empty. If the cell was empty and `f` failed, an
/// error is returned.
///
/// If several threads concurrently run `get_or_init`, more than one `f` can
/// be called. However, all threads will return the same value, produced by
/// some `f`.
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&'a T, E>
where
F: FnOnce() -> Result<&'a T, E>,
{
let f = || f().map(|value| NonZeroUsize::new(value as *const T as usize).unwrap());
let ptr = self.inner.get_or_try_init(f)?;
unsafe { Ok(&*(ptr.get() as *const T)) }
}

/// ```compile_fail
/// use once_cell::race::OnceRef;
///
/// let mut l = OnceRef::new();
///
/// {
/// let y = 2;
/// let mut r = OnceRef::new();
/// r.set(&y).unwrap();
/// core::mem::swap(&mut l, &mut r);
/// }
///
/// // l now contains a dangling reference to y
/// eprintln!("uaf: {}", l.get().unwrap());
/// ```
fn _dummy() {}
}

#[cfg(feature = "alloc")]
pub use self::once_box::OnceBox;

Expand Down

0 comments on commit fae9f73

Please sign in to comment.