//! Asynchronous implementation of OnceCell and Lazy. //! //! This package provides an asynchronous implementation of OnceCell and Lazy, which is usefull in cases where you would like to instantiate //! these cells with asynchronous code. //! //! This package is currently still in development and should _not_ be used in production code. While heavily inspired by existing //! OnceCell packages, it should not be seen as _safe_. My understanding of unsafe Rust is still rudimentary, and while //! I have done my best to justify the unsafe code in this crate, I currently do not have the knowledge to fully do so. #![warn(missing_docs)] #![crate_name = "async_oncecell"] use std::{ cell::UnsafeCell, convert::Infallible, fmt, future::Future, pin::Pin, sync::atomic::{AtomicBool, Ordering}, }; use futures::lock::Mutex; /// Cell which can be lazy instantiated with an asynchronous block and is safely share-able between threads. pub struct OnceCell { lock: Mutex<()>, initialized: AtomicBool, inner: UnsafeCell>, } // Justification: UnsafeCell is not Sync, however is only mutable in set which is guarded by the lock. unsafe impl Sync for OnceCell {} unsafe impl Send for OnceCell {} impl OnceCell { /// Creates a new empty OnceCell. Currently this function is not const due to Mutex limitations, /// so to share between multiple threads an Arc needs to be used. pub fn new() -> Self { // TODO: Mutex is not const, need to find suitable lock which can be const Self { lock: Mutex::new(()), initialized: AtomicBool::new(false), inner: UnsafeCell::new(None), } } /// Get or initialize this cell with the given asynchronous block. If the cell is already initialized, the current value will be returned. /// Otherwise the asynchronous block is used to initialize the OnceCell. This function will always return a value. /// /// # Example /// ```rust /// # use async_oncecell::*; /// # tokio_test::block_on(async { /// let cell = OnceCell::new(); /// cell.get_or_init(async { /// 0 // expensive calculation /// }).await; /// assert_eq!(cell.get(), Some(&0)); /// # }) /// ``` pub async fn get_or_init(&self, f: F) -> &T where F: Future, { match self .get_or_try_init(async { Ok::<_, Infallible>(f.await) }) .await { Ok(res) => res, Err(_) => unreachable!(), } } /// Get or initialize this cell with the given asynchronous block. If the cell is already initialized, the current value will be returned. /// Otherwise the asynchronous block is used to initialize the OnceCell. This function will always return a value. /// /// # Example /// ```rust /// # use async_oncecell::*; /// # use std::convert::Infallible; /// # tokio_test::block_on(async { /// let cell = OnceCell::new(); /// cell.get_or_try_init(async { /// Ok::<_, Infallible>(0) // expensive calculation /// }).await; /// assert_eq!(cell.get(), Some(&0)); /// # }) /// ``` pub async fn get_or_try_init(&self, f: F) -> Result<&T, E> where F: Future>, { if !self.initialized.load(Ordering::Acquire) { self.set(f).await?; } // Initializer did not return an error which means we can safely unwrap the value. Ok(self.get().unwrap()) } /// Returns the value of the OnceCell or None if the OnceCell has not been initialized yet. pub fn get(&self) -> Option<&T> { unsafe { &*self.inner.get() }.as_ref() } async fn set(&self, f: F) -> Result<(), E> where F: Future>, { // Lock function (gets unlocked at the end of the function) let _guard = self.lock.lock().await; if !self.initialized.load(Ordering::Acquire) { match f.await { Ok(v) => { // Justification: Mutation is guarded by lock, and has no references pointed to it since value cannot be set (Some) yet. unsafe { *self.inner.get() = Some(v); }; self.initialized.store(true, Ordering::Release); Ok(()) } Err(e) => Err(e), } } else { Ok(()) } } /// If the OnceCell is already initialized, either by having have called [`get_or_init()`](Self::get_or_init()) or [`get_or_try_init()`](Self::get_or_try_init()). pub fn initialized(&self) -> bool { self.initialized.load(Ordering::Acquire) } } impl Default for OnceCell { fn default() -> Self { Self::new() } } impl fmt::Debug for OnceCell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("OnceCell").field(&self.get()).finish() } } impl PartialEq for OnceCell { fn eq(&self, other: &Self) -> bool { self.get() == other.get() } } impl Eq for OnceCell {} /// Lazy cell which is only instantiated upon first retreival of contained value pub struct Lazy + Send>>> { cell: OnceCell, f: Mutex>, } impl Lazy + Send>>> { /// Creates a new Lazy with the given instantiator. /// /// # Example /// ```rust /// # use async_oncecell::*; /// # use std::convert::Infallible; /// # tokio_test::block_on(async { /// let lazy = Lazy::new(async { /// 0 // Expensive calculation /// }); /// assert_eq!(lazy.get().await, &0); /// # }) /// ``` pub fn new(f: impl Future + 'static + Send) -> Self { Self { cell: OnceCell::new(), f: Mutex::new(Some(Box::pin(f))), } } } impl Lazy + Send>>> { /// Retreives the contents of the Lazy. If not instantiated, the instantiator block will be executed first. pub async fn get(&self) -> &T { self.cell .get_or_init(async { self.f.lock().await.take().unwrap().await }) .await } } impl fmt::Debug for Lazy { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Lazy").field(&self.cell.get()).finish() } } #[cfg(test)] mod test { use std::sync::Arc; use super::*; #[tokio::test] async fn test_once_cell() { let cell: OnceCell = OnceCell::new(); assert_eq!(cell.get(), None); let v = cell.get_or_init(async { 0 }).await; assert_eq!(v, &0); assert_eq!(cell.get(), Some(&0)); } #[tokio::test] async fn test_once_cell_across_threads() { let cell: Arc> = Arc::new(OnceCell::new()); let cell_clone1 = cell.clone(); let handler = tokio::spawn(async move { cell_clone1.get_or_init(async { 0 }).await; }); assert!(handler.await.is_ok()); assert_eq!(cell.get(), Some(&0)); } #[tokio::test] async fn test_lazy() { let lazy = Lazy::new(async { 0 }); assert_eq!(lazy.get().await, &0); assert_eq!(lazy.get().await, &0); } #[tokio::test] async fn test_lazy_multi_threaded() { let t = 5; let lazy = Arc::new(Lazy::new(async move { t })); let lazy_clone = lazy.clone(); let handle = tokio::spawn(async move { assert_eq!(lazy_clone.get().await, &t); }); assert!(handle.await.is_ok()); assert_eq!(lazy.get().await, &t); } #[tokio::test] async fn test_lazy_struct() { struct Test { lazy: Lazy, } let data = Test { lazy: Lazy::new(async { 0 }), }; assert_eq!(data.lazy.get().await, &0); } }