async_oncecell/
lib.rs

1//! Asynchronous implementation of OnceCell and Lazy.
2//!
3//! This package provides an asynchronous implementation of OnceCell and Lazy, which is usefull in cases where you would like to instantiate
4//! these cells with asynchronous code.
5//!
6//! This package is currently still in development and should _not_ be used in production code. While heavily inspired by existing
7//! OnceCell packages, it should not be seen as _safe_. My understanding of unsafe Rust is still rudimentary, and while
8//! I have done my best to justify the unsafe code in this crate, I currently do not have the knowledge to fully do so.
9
10#![warn(missing_docs)]
11#![crate_name = "async_oncecell"]
12
13use std::{
14    cell::UnsafeCell,
15    convert::Infallible,
16    fmt,
17    future::Future,
18    pin::Pin,
19    sync::atomic::{AtomicBool, Ordering},
20};
21
22use futures::lock::Mutex;
23
24/// Cell which can be lazy instantiated with an asynchronous block and is safely share-able between threads.
25pub struct OnceCell<T> {
26    lock: Mutex<()>,
27    initialized: AtomicBool,
28    inner: UnsafeCell<Option<T>>,
29}
30
31// Justification: UnsafeCell is not Sync, however is only mutable in set which is guarded by the lock.
32unsafe impl<T: Sync + Send> Sync for OnceCell<T> {}
33unsafe impl<T: Send> Send for OnceCell<T> {}
34
35impl<T> OnceCell<T> {
36    /// Creates a new empty OnceCell. Currently this function is not const due to Mutex limitations,
37    /// so to share between multiple threads an Arc needs to be used.
38    pub fn new() -> Self {
39        // TODO: Mutex is not const, need to find suitable lock which can be const
40        Self {
41            lock: Mutex::new(()),
42            initialized: AtomicBool::new(false),
43            inner: UnsafeCell::new(None),
44        }
45    }
46
47    /// Get or initialize this cell with the given asynchronous block. If the cell is already initialized, the current value will be returned.
48    /// Otherwise the asynchronous block is used to initialize the OnceCell. This function will always return a value.
49    ///
50    /// # Example
51    /// ```rust
52    /// # use async_oncecell::*;
53    /// # tokio_test::block_on(async {
54    /// let cell = OnceCell::new();
55    /// cell.get_or_init(async {
56    ///     0 // expensive calculation
57    /// }).await;
58    /// assert_eq!(cell.get(), Some(&0));
59    /// # })
60    /// ```
61    pub async fn get_or_init<F>(&self, f: F) -> &T
62    where
63        F: Future<Output = T>,
64    {
65        match self
66            .get_or_try_init(async { Ok::<_, Infallible>(f.await) })
67            .await
68        {
69            Ok(res) => res,
70            Err(_) => unreachable!(),
71        }
72    }
73
74    /// Get or initialize this cell with the given asynchronous block. If the cell is already initialized, the current value will be returned.
75    /// Otherwise the asynchronous block is used to initialize the OnceCell. This function will always return a value.
76    ///
77    /// # Example
78    /// ```rust
79    /// # use async_oncecell::*;
80    /// # use std::convert::Infallible;
81    /// # tokio_test::block_on(async {
82    /// let cell = OnceCell::new();
83    /// cell.get_or_try_init(async {
84    ///     Ok::<_, Infallible>(0) // expensive calculation
85    /// }).await;
86    /// assert_eq!(cell.get(), Some(&0));
87    /// # })
88    /// ```
89    pub async fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
90    where
91        F: Future<Output = Result<T, E>>,
92    {
93        if !self.initialized.load(Ordering::Acquire) {
94            self.set(f).await?;
95        }
96
97        // Initializer did not return an error which means we can safely unwrap the value.
98        Ok(self.get().unwrap())
99    }
100
101    /// Returns the value of the OnceCell or None if the OnceCell has not been initialized yet.
102    pub fn get(&self) -> Option<&T> {
103        unsafe { &*self.inner.get() }.as_ref()
104    }
105
106    async fn set<F, E>(&self, f: F) -> Result<(), E>
107    where
108        F: Future<Output = Result<T, E>>,
109    {
110        // Lock function (gets unlocked at the end of the function)
111        let _guard = self.lock.lock().await;
112
113        if !self.initialized.load(Ordering::Acquire) {
114            match f.await {
115                Ok(v) => {
116                    // Justification: Mutation is guarded by lock, and has no references pointed to it since value cannot be set (Some) yet.
117                    unsafe {
118                        *self.inner.get() = Some(v);
119                    };
120                    self.initialized.store(true, Ordering::Release);
121                    Ok(())
122                }
123                Err(e) => Err(e),
124            }
125        } else {
126            Ok(())
127        }
128    }
129
130    /// 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()).
131    pub fn initialized(&self) -> bool {
132        self.initialized.load(Ordering::Acquire)
133    }
134}
135
136impl<T> Default for OnceCell<T> {
137    fn default() -> Self {
138        Self::new()
139    }
140}
141
142impl<T: fmt::Debug> fmt::Debug for OnceCell<T> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.debug_tuple("OnceCell").field(&self.get()).finish()
145    }
146}
147
148impl<T: PartialEq> PartialEq for OnceCell<T> {
149    fn eq(&self, other: &Self) -> bool {
150        self.get() == other.get()
151    }
152}
153
154impl<T: Eq> Eq for OnceCell<T> {}
155
156/// Lazy cell which is only instantiated upon first retreival of contained value
157pub struct Lazy<T, F = Pin<Box<dyn Future<Output = T> + Send>>> {
158    cell: OnceCell<T>,
159    f: Mutex<Option<F>>,
160}
161
162impl<T> Lazy<T, Pin<Box<dyn Future<Output = T> + Send>>> {
163    /// Creates a new Lazy with the given instantiator.
164    ///
165    /// # Example
166    /// ```rust
167    /// # use async_oncecell::*;
168    /// # use std::convert::Infallible;
169    /// # tokio_test::block_on(async {
170    /// let lazy = Lazy::new(async {
171    ///     0 // Expensive calculation    
172    /// });
173    /// assert_eq!(lazy.get().await, &0);
174    /// # })
175    /// ```
176    pub fn new(f: impl Future<Output = T> + 'static + Send) -> Self {
177        Self {
178            cell: OnceCell::new(),
179            f: Mutex::new(Some(Box::pin(f))),
180        }
181    }
182}
183
184impl<T> Lazy<T, Pin<Box<dyn Future<Output = T> + Send>>> {
185    /// Retreives the contents of the Lazy. If not instantiated, the instantiator block will be executed first.
186    pub async fn get(&self) -> &T {
187        self.cell
188            .get_or_init(async { self.f.lock().await.take().unwrap().await })
189            .await
190    }
191}
192
193impl<T: fmt::Debug, F> fmt::Debug for Lazy<T, F> {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        f.debug_tuple("Lazy").field(&self.cell.get()).finish()
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use std::sync::Arc;
202
203    use super::*;
204
205    #[tokio::test]
206    async fn test_once_cell() {
207        let cell: OnceCell<i32> = OnceCell::new();
208        assert_eq!(cell.get(), None);
209
210        let v = cell.get_or_init(async { 0 }).await;
211        assert_eq!(v, &0);
212        assert_eq!(cell.get(), Some(&0));
213    }
214
215    #[tokio::test]
216    async fn test_once_cell_across_threads() {
217        let cell: Arc<OnceCell<i32>> = Arc::new(OnceCell::new());
218        let cell_clone1 = cell.clone();
219
220        let handler = tokio::spawn(async move {
221            cell_clone1.get_or_init(async { 0 }).await;
222        });
223
224        assert!(handler.await.is_ok());
225        assert_eq!(cell.get(), Some(&0));
226    }
227
228    #[tokio::test]
229    async fn test_lazy() {
230        let lazy = Lazy::new(async { 0 });
231        assert_eq!(lazy.get().await, &0);
232        assert_eq!(lazy.get().await, &0);
233    }
234
235    #[tokio::test]
236    async fn test_lazy_multi_threaded() {
237        let t = 5;
238        let lazy = Arc::new(Lazy::new(async move { t }));
239        let lazy_clone = lazy.clone();
240
241        let handle = tokio::spawn(async move {
242            assert_eq!(lazy_clone.get().await, &t);
243        });
244
245        assert!(handle.await.is_ok());
246        assert_eq!(lazy.get().await, &t);
247    }
248
249    #[tokio::test]
250    async fn test_lazy_struct() {
251        struct Test {
252            lazy: Lazy<i32>,
253        }
254
255        let data = Test {
256            lazy: Lazy::new(async { 0 }),
257        };
258
259        assert_eq!(data.lazy.get().await, &0);
260    }
261}