#![cfg_attr(not(test), no_std)]
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use portable_atomic::{AtomicBool, Ordering};
pub struct StaticCell<T> {
used: AtomicBool,
val: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T> Send for StaticCell<T> {}
unsafe impl<T> Sync for StaticCell<T> {}
impl<T> StaticCell<T> {
#[inline]
pub const fn new() -> Self {
Self {
used: AtomicBool::new(false),
val: UnsafeCell::new(MaybeUninit::uninit()),
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn init(&'static self, val: T) -> &'static mut T {
self.uninit().write(val)
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn init_with(&'static self, val: impl FnOnce() -> T) -> &'static mut T {
self.uninit().write(val())
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn uninit(&'static self) -> &'static mut MaybeUninit<T> {
if let Some(val) = self.try_uninit() {
val
} else {
panic!("`StaticCell` is already full, it can't be initialized twice.");
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn try_init(&'static self, val: T) -> Option<&'static mut T> {
Some(self.try_uninit()?.write(val))
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn try_init_with(&'static self, val: impl FnOnce() -> T) -> Option<&'static mut T> {
Some(self.try_uninit()?.write(val()))
}
#[inline]
pub fn try_uninit(&'static self) -> Option<&'static mut MaybeUninit<T>> {
if self
.used
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
let val = unsafe { &mut *self.val.get() };
Some(val)
} else {
None
}
}
}
pub struct ConstStaticCell<T> {
taken: AtomicBool,
val: UnsafeCell<T>,
}
unsafe impl<T: Send> Send for ConstStaticCell<T> {}
unsafe impl<T: Send> Sync for ConstStaticCell<T> {}
impl<T> ConstStaticCell<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self {
taken: AtomicBool::new(false),
val: UnsafeCell::new(value),
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn take(&'static self) -> &'static mut T {
if let Some(val) = self.try_take() {
val
} else {
panic!("`ConstStaticCell` is already taken, it can't be taken twice")
}
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub fn try_take(&'static self) -> Option<&'static mut T> {
if self
.taken
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
let val = unsafe { &mut *self.val.get() };
Some(val)
} else {
None
}
}
}
#[cfg(feature = "nightly")]
#[cfg_attr(docsrs, doc(cfg(feature = "nightly")))]
#[macro_export]
macro_rules! make_static {
($val:expr) => ($crate::make_static!($val, ));
($val:expr, $(#[$m:meta])*) => {{
type T = impl ::core::marker::Sized;
$(#[$m])*
static STATIC_CELL: $crate::StaticCell<T> = $crate::StaticCell::new();
#[deny(unused_attributes)]
let (x,) = STATIC_CELL.uninit().write(($val,));
x
}};
}
#[cfg(test)]
mod tests {
use crate::StaticCell;
#[test]
fn test_static_cell() {
static CELL: StaticCell<u32> = StaticCell::new();
let val: &'static u32 = CELL.init(42u32);
assert_eq!(*val, 42);
}
#[cfg(feature = "nightly")]
#[test]
fn test_make_static() {
let val: &'static u32 = make_static!(42u32);
assert_eq!(*val, 42);
}
}
#[cfg(doctest)]
mod compile_fail_tests {
fn send_requires_send() {}
fn sync_requires_send() {}
}