diff --git a/src/lib.rs b/src/lib.rs index bf99ee7..3649294 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,9 @@ use portable_atomic::{AtomicBool, Ordering}; /// to the contents permanently changes it to "full". This allows that reference to be valid /// forever. /// +/// If your value can be initialized as a `const` value, consider using [`ConstInitCell`] +/// instead if you only need to take the value at runtime. +/// /// See the [crate-level docs](crate) for usage. pub struct StaticCell { used: AtomicBool, @@ -131,6 +134,74 @@ impl StaticCell { } } +// --- + +/// Statically allocated and initialized, taken at runtime cell. +/// +/// It has two states: "untake" and "taken". It is created "untake", and obtaining a reference +/// to the contents permanently changes it to "taken". This allows that reference to be valid +/// forever. +/// +/// If your value can be const defined, for example a large, zero filled buffer used for DMA +/// or other scratch memory usage, `ConstInitCell` can be used to guarantee the initializer +/// will never take up stack memory. +/// +/// If your values are all zero initialized, the resulting `ConstInitCell` should be placed +/// in `.bss`, not taking flash space for initialization either. +/// +/// See the [crate-level docs](crate) for usage. +pub struct ConstInitCell { + taken: AtomicBool, + val: UnsafeCell, +} + +unsafe impl Send for ConstInitCell {} +unsafe impl Sync for ConstInitCell {} + +impl ConstInitCell { + /// Create a new, empty `ConstInitCell`. + /// + /// It can be taken at runtime with [`ConstInitCell::take()`] or similar methods. + #[inline] + pub const fn new(value: T) -> Self { + Self { + taken: AtomicBool::new(false), + val: UnsafeCell::new(value), + } + } + + /// Take the `ConstInitCell`, returning a mutable reference to it. + /// + /// # Panics + /// + /// Panics if this `ConstInitCell` was already taken. + #[inline] + #[allow(clippy::mut_from_ref)] + pub fn take(&'static self) -> &'static mut T { + if let Some(val) = self.try_take() { + val + } else { + panic!("`ConstInitCell` 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() + { + // SAFETY: We just checked that the value is not yet taken and marked it as taken. + let val = unsafe { &mut *self.val.get() }; + Some(val) + } else { + None + } + } +} + /// Convert a `T` to a `&'static mut T`. /// /// The macro declares a `static StaticCell` and then initializes it when run, returning the `&'static mut`.