Skip to content

Commit

Permalink
Add "first one wins" no_std-compatible flavor
Browse files Browse the repository at this point in the history
  • Loading branch information
matklad committed Nov 11, 2020
1 parent 63205d5 commit 7c4dd96
Show file tree
Hide file tree
Showing 5 changed files with 414 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 1.5.0

- add new `once_cell::race` module for "first one wins" no_std-compatible initialization flavor.
The API is provisional, subject to change and is gated by the `unstable` cargo feature.

## 1.4.1

- upgrade `parking_lot` to `0.11.0`
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "once_cell"
version = "1.4.1"
version = "1.5.0"
authors = ["Aleksey Kladov <aleksey.kladov@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
Expand Down Expand Up @@ -33,6 +33,8 @@ regex = "1.2.0"
default = ["std"]
# Enables `once_cell::sync` module.
std = []
# Enables semver-exempt APIs of this crate
unstable = []

[[example]]
name = "bench"
Expand Down Expand Up @@ -61,3 +63,6 @@ required-features = ["std"]
[[example]]
name = "test_synchronization"
required-features = ["std"]

[package.metadata.docs.rs]
all-features = true
182 changes: 182 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,3 +1039,185 @@ pub mod sync {
/// ```
fn _dummy() {}
}

/// "First one wins" flavor of `OnceCell`.
///
/// If two threads race to initialize a type from the `race` module, they
/// don't block, execute initialization function together, but only one of
/// them stores the result.
///
/// This module does not require `std` feature.
pub mod race {
use core::{
num::NonZeroUsize,
sync::atomic::{AtomicUsize, Ordering},
};
#[cfg(feature = "std")]
use std::{marker::PhantomData, ptr, sync::atomic::AtomicPtr};

#[derive(Default, Debug)]
pub struct OnceNonZeroUsize {
inner: AtomicUsize,
}

impl OnceNonZeroUsize {
pub const fn new() -> OnceNonZeroUsize {
OnceNonZeroUsize { inner: AtomicUsize::new(0) }
}

pub fn get(&self) -> Option<NonZeroUsize> {
let val = self.inner.load(Ordering::Acquire);
NonZeroUsize::new(val)
}

pub fn set(&self, value: NonZeroUsize) -> Result<(), ()> {
let val = self.inner.compare_and_swap(0, value.get(), Ordering::AcqRel);
if val == 0 {
Ok(())
} else {
Err(())
}
}

pub fn get_or_init<F>(&self, f: F) -> NonZeroUsize
where
F: FnOnce() -> NonZeroUsize,
{
enum Void {}
match self.get_or_try_init(|| Ok::<NonZeroUsize, Void>(f())) {
Ok(val) => val,
Err(void) => match void {},
}
}

pub fn get_or_try_init<F, E>(&self, f: F) -> Result<NonZeroUsize, E>
where
F: FnOnce() -> Result<NonZeroUsize, E>,
{
let val = self.inner.load(Ordering::Acquire);
let res = match NonZeroUsize::new(val) {
Some(it) => it,
None => {
let mut val = f()?.get();
let old_val = self.inner.compare_and_swap(0, val, Ordering::AcqRel);
if old_val != 0 {
val = old_val;
}
unsafe { NonZeroUsize::new_unchecked(val) }
}
};
Ok(res)
}
}

#[derive(Default, Debug)]
pub struct OnceBool {
inner: OnceNonZeroUsize,
}

impl OnceBool {
fn from_usize(value: NonZeroUsize) -> bool {
value.get() == 1
}
fn to_usize(value: bool) -> NonZeroUsize {
unsafe { NonZeroUsize::new_unchecked(if value { 1 } else { 2 }) }
}

pub const fn new() -> OnceBool {
OnceBool { inner: OnceNonZeroUsize::new() }
}

pub fn get(&self) -> Option<bool> {
self.inner.get().map(OnceBool::from_usize)
}

pub fn set(&self, value: bool) -> Result<(), ()> {
self.inner.set(OnceBool::to_usize(value))
}

pub fn get_or_init<F>(&self, f: F) -> bool
where
F: FnOnce() -> bool,
{
OnceBool::from_usize(self.inner.get_or_init(|| OnceBool::to_usize(f())))
}

pub fn get_or_try_init<F, E>(&self, f: F) -> Result<bool, E>
where
F: FnOnce() -> Result<bool, E>,
{
self.inner.get_or_try_init(|| f().map(OnceBool::to_usize)).map(OnceBool::from_usize)
}
}

#[derive(Default, Debug)]
#[cfg(feature = "std")]
pub struct OnceBox<T> {
inner: AtomicPtr<T>,
ghost: PhantomData<Option<Box<T>>>,
}

#[cfg(feature = "std")]
impl<T> Drop for OnceBox<T> {
fn drop(&mut self) {
let ptr = *self.inner.get_mut();
if !ptr.is_null() {
drop(unsafe { Box::from_raw(ptr) })
}
}
}

#[cfg(feature = "std")]
impl<T> OnceBox<T> {
pub const fn new() -> OnceBox<T> {
OnceBox { inner: AtomicPtr::new(ptr::null_mut()), ghost: PhantomData }
}

pub fn get(&self) -> Option<&T> {
let ptr = self.inner.load(Ordering::Acquire);
if ptr.is_null() {
return None;
}
Some(unsafe { &*ptr })
}

// Result<(), Box<T>> here?
pub fn set(&self, value: T) -> Result<(), ()> {
let ptr = Box::into_raw(Box::new(value));
if ptr.is_null() {
drop(unsafe { Box::from_raw(ptr) });
return Err(());
}
Ok(())
}

pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
enum Void {}
match self.get_or_try_init(|| Ok::<T, Void>(f())) {
Ok(val) => val,
Err(void) => match void {},
}
}

pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
let mut ptr = self.inner.load(Ordering::Acquire);

if ptr.is_null() {
let val = f()?;
ptr = Box::into_raw(Box::new(val));
let old_ptr = self.inner.compare_and_swap(ptr::null_mut(), ptr, Ordering::AcqRel);
if !old_ptr.is_null() {
drop(unsafe { Box::from_raw(ptr) });
ptr = old_ptr;
}
};
Ok(unsafe { &*ptr })
}
}
}
Loading

0 comments on commit 7c4dd96

Please sign in to comment.