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}