use std::borrow::Borrow;
use std::cell::UnsafeCell;
/// A mutable memory location that can be set only once.
pub struct OnceCell<T>(UnsafeCell<Option<T>>);
impl<T> OnceCell<T> {
/// Creates a new `OnceCell` that may already be initialized.
///
/// # Example
///
/// ```
/// use mitochondria::OnceCell;
///
/// let c = OnceCell::new(Some("Hello vesicle!".to_owned()));
/// ```
#[inline]
pub fn new(value: Option<T>) -> Self {
OnceCell(UnsafeCell::new(value))
}
/// Performs an initialization routine once and only once. The given closure
/// will be executed if this is the first time `init_once` has been called,
/// and otherwise the routine will *not* be invoked.
///
/// The result of the given closure is then used to set the contents of this
/// cell and borrowed as the return value of this method.
///
/// # Examples
///
/// ```
/// use mitochondria::OnceCell;
///
/// let c = OnceCell::new(None);
///
/// let greeting: &str = c.init_once(|| "Hello ribosome!".to_owned());
/// ```
///
/// ```
/// use mitochondria::OnceCell;
///
/// let c = OnceCell::new(Some("Hello reticulum!".to_owned()));
///
/// // Calls to `init_once` on already-initialized cells are ignored.
/// assert_eq!(c.init_once(|| "Goodbye!".to_owned()), "Hello reticulum!");
/// ```
pub fn init_once<F: FnOnce() -> T>(&self, f: F) -> &T {
let mut cell = unsafe { &mut *self.0.get() };
if cell.is_none() {
let value = f();
// f() may have initialised the value already.
if cell.is_none() {
*cell = Some(value);
}
}
cell.as_ref().unwrap().borrow()
}
/// Borrows the contained value, if initialized.
///
/// # Examples
///
/// ```
/// use mitochondria::OnceCell;
///
/// let c = OnceCell::new(None);
///
/// assert!(c.borrow().is_none());
///
/// let greeting = c.init_once(|| "Hello nucleus!".to_owned());
/// assert_eq!(c.borrow(), Some(greeting));
/// ```
#[inline]
pub fn borrow(&self) -> Option<&T> {
unsafe { (*self.0.get()).as_ref() }
}
}
#[cfg(test)]
mod tests {
use OnceCell;
#[test]
fn reentrancy() {
let c = OnceCell::new(None);
let value = *c.init_once(|| {
c.init_once(|| "ribosome");
"nucleolus"
});
assert_eq!(value, "ribosome");
}
}