use std::fmt;
use std::future::Future;
use std::future::Pending;
use std::future::Ready;
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;
use std::pin::Pin;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use crate::internal::value_cell::ValueCell;
use crate::mutex::Mutex;
pub struct LazyCell<T, Fut, F = fn() -> Fut> {
value: ValueCell<T>,
state: Mutex<State<F, Fut>>,
poisoned: AtomicBool,
}
enum State<F, Fut> {
Initializer(Option<F>),
Attempt(Fut),
Complete,
}
impl<T, Fut, F> LazyCell<T, Fut, F> {
pub const fn new(initializer: F) -> Self
where
F: FnOnce() -> Fut,
Fut: Future<Output = T>,
{
Self {
value: ValueCell::new(),
state: Mutex::new(State::Initializer(Some(initializer))),
poisoned: AtomicBool::new(false),
}
}
pub fn get(this: &Self) -> Option<&T> {
this.value.get()
}
pub fn get_mut(this: &mut Self) -> Option<&mut T> {
this.value.get_mut()
}
fn assert_unpoisoned(&self) {
if self.poisoned.load(Ordering::Relaxed) {
panic_poisoned();
}
}
}
impl<T, Fut, F> LazyCell<T, Fut, F>
where
F: FnOnce() -> Fut,
Fut: Future<Output = T> + Unpin,
{
pub async fn force(this: &Self) -> &T {
Self::force_pin(Pin::new(this)).await
}
pub async fn force_mut(this: &mut Self) -> &mut T {
Self::force_pin_mut(Pin::new(this)).await
}
}
impl<T, Fut, F> LazyCell<T, Fut, F>
where
F: FnOnce() -> Fut,
Fut: Future<Output = T>,
{
pub async fn force_pin<'a>(this: Pin<&'a Self>) -> &'a T {
let this_ref: &'a Self = Pin::get_ref(this);
if let Some(value) = Self::get(this_ref) {
return value;
}
this_ref.assert_unpoisoned();
let mut state = this_ref.state.lock().await;
if let Some(value) = Self::get(this_ref) {
return value;
}
this_ref.assert_unpoisoned();
let state = unsafe { Pin::new_unchecked(&mut *state) };
let value = drive_pinned_attempt(state, &this_ref.poisoned).await;
unsafe { this_ref.value.set(value) }
}
pub async fn force_pin_mut<'a>(this: Pin<&'a mut Self>) -> &'a mut T {
let this: &'a mut Self = unsafe { Pin::get_unchecked_mut(this) };
if this.value.is_initialized_mut() {
return this
.value
.get_mut()
.expect("LazyCell value missing while initialized");
}
if *this.poisoned.get_mut() {
panic_poisoned();
}
let state = unsafe { Pin::new_unchecked(this.state.get_mut()) };
let value = drive_pinned_attempt(state, &this.poisoned).await;
this.value.set_mut(value)
}
}
async fn drive_pinned_attempt<T, F, Fut>(
mut state: Pin<&mut State<F, Fut>>,
poisoned: &AtomicBool,
) -> T
where
F: FnOnce() -> Fut,
Fut: Future<Output = T>,
{
let initializer = match unsafe { state.as_mut().get_unchecked_mut() } {
State::Initializer(initializer) => Some(
initializer
.take()
.expect("LazyCell initializer missing while uninitialized"),
),
State::Attempt(_) => None,
State::Complete => panic!("LazyCell state complete while value is uninitialized"),
};
if let Some(initializer) = initializer {
let future = {
let _poison = PoisonOnPanic(poisoned);
initializer()
};
*unsafe { state.as_mut().get_unchecked_mut() } = State::Attempt(future);
}
let value = std::future::poll_fn(|cx| {
let _poison = PoisonOnPanic(poisoned);
let attempt = match unsafe { state.as_mut().get_unchecked_mut() } {
State::Attempt(attempt) => attempt,
State::Initializer(_) => panic!("LazyCell attempt missing while initializing"),
State::Complete => panic!("LazyCell state complete while value is uninitialized"),
};
unsafe { Pin::new_unchecked(attempt) }.poll(cx)
})
.await;
let _poison = PoisonOnPanic(poisoned);
*unsafe { state.as_mut().get_unchecked_mut() } = State::Complete;
value
}
impl<T> Default for LazyCell<T, Ready<T>>
where
T: Default,
{
fn default() -> Self {
fn initialize<T: Default>() -> Ready<T> {
std::future::ready(T::default())
}
Self::new(initialize::<T>)
}
}
impl<T, Fut> LazyCell<T, Fut>
where
Fut: Future<Output = T>,
{
pub fn from_future(future: Fut) -> Self {
Self {
value: ValueCell::new(),
state: Mutex::new(State::Attempt(future)),
poisoned: AtomicBool::new(false),
}
}
}
impl<T> LazyCell<T, Pending<T>> {
pub const fn from_value(value: T) -> Self {
Self {
value: ValueCell::from_value(value),
state: Mutex::new(State::Complete),
poisoned: AtomicBool::new(false),
}
}
}
impl<T: fmt::Debug, Fut, F> fmt::Debug for LazyCell<T, Fut, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut tuple = f.debug_tuple("LazyCell");
match Self::get(self) {
Some(value) => tuple.field(value),
None => tuple.field(&format_args!("<uninit>")),
};
tuple.finish()
}
}
impl<T, Fut: Unpin, F> Unpin for LazyCell<T, Fut, F> {}
impl<T: UnwindSafe, Fut: UnwindSafe, F: UnwindSafe> UnwindSafe for LazyCell<T, Fut, F> {}
impl<T: RefUnwindSafe + UnwindSafe, Fut: UnwindSafe, F: UnwindSafe> RefUnwindSafe
for LazyCell<T, Fut, F>
{
}
struct PoisonOnPanic<'a>(&'a AtomicBool);
impl Drop for PoisonOnPanic<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
self.0.store(true, Ordering::Relaxed);
}
}
}
#[cold]
#[inline(never)]
fn panic_poisoned() -> ! {
panic!("LazyCell instance has previously been poisoned")
}