1#![allow(clippy::needless_return)]
2
3use dioxus_core::CapturedError;
4use std::{hint::black_box, prelude::rust_2024::Future, sync::atomic::AtomicBool};
5
6pub struct Lazy<T> {
16 value: std::sync::OnceLock<T>,
17 started_initialization: AtomicBool,
18 constructor: Option<fn() -> Result<T, CapturedError>>,
19 _phantom: std::marker::PhantomData<T>,
20}
21
22impl<T: Send + Sync + 'static> Lazy<T> {
23 #[allow(clippy::self_named_constructors)]
27 pub const fn lazy() -> Self {
28 Self {
29 _phantom: std::marker::PhantomData,
30 constructor: None,
31 started_initialization: AtomicBool::new(false),
32 value: std::sync::OnceLock::new(),
33 }
34 }
35
36 pub const fn new<F, G, E>(constructor: F) -> Self
37 where
38 F: Fn() -> G + Copy,
39 G: Future<Output = Result<T, E>> + Send + 'static,
40 E: Into<CapturedError>,
41 {
42 if std::mem::size_of::<F>() != 0 {
43 panic!("The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
44 }
45
46 black_box(constructor);
48
49 Self {
50 _phantom: std::marker::PhantomData,
51 value: std::sync::OnceLock::new(),
52 started_initialization: AtomicBool::new(false),
53 constructor: Some(blocking_initialize::<T, F, G, E>),
54 }
55 }
56
57 pub fn set(&self, pool: T) -> Result<(), CapturedError> {
62 let res = self.value.set(pool);
63 if res.is_err() {
64 return Err(anyhow::anyhow!("Lazy value is already initialized.").into());
65 }
66
67 Ok(())
68 }
69
70 pub fn try_set(&self, pool: T) -> Result<(), T> {
71 self.value.set(pool)
72 }
73
74 pub fn initialize(&self) -> Result<(), CapturedError> {
76 if let Some(constructor) = self.constructor {
77 if self
79 .started_initialization
80 .swap(true, std::sync::atomic::Ordering::SeqCst)
81 {
82 self.value.wait();
83 return Ok(());
84 }
85
86 self.set(constructor().unwrap())?;
88 }
89 Ok(())
90 }
91
92 pub fn get(&self) -> &T {
95 if self.constructor.is_none() {
96 return self.value.get().expect("Lazy value is not initialized. Make sure to call `initialize` before dereferencing.");
97 };
98
99 if self.value.get().is_none() {
100 self.initialize().expect("Failed to initialize lazy value");
101 }
102
103 self.value.get().unwrap()
104 }
105}
106
107impl<T: Send + Sync + 'static> Default for Lazy<T> {
108 fn default() -> Self {
109 Self::lazy()
110 }
111}
112
113impl<T: Send + Sync + 'static> std::ops::Deref for Lazy<T> {
114 type Target = T;
115
116 fn deref(&self) -> &Self::Target {
117 self.get()
118 }
119}
120
121impl<T: std::fmt::Debug + Send + Sync + 'static> std::fmt::Debug for Lazy<T> {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("Lazy").field("value", self.get()).finish()
124 }
125}
126
127fn blocking_initialize<T, F, G, E>() -> Result<T, CapturedError>
132where
133 T: Send + Sync + 'static,
134 F: Fn() -> G + Copy,
135 G: Future<Output = Result<T, E>> + Send + 'static,
136 E: Into<CapturedError>,
137{
138 assert_eq!(std::mem::size_of::<F>(), 0, "The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables.");
139
140 #[cfg(feature = "server")]
141 {
142 let ptr: F = unsafe { std::mem::zeroed() };
143 let fut = ptr();
144 return std::thread::spawn(move || {
145 tokio::runtime::Builder::new_current_thread()
146 .enable_all()
147 .build()
148 .unwrap()
149 .block_on(fut)
150 .map_err(|e| e.into())
151 })
152 .join()
153 .unwrap();
154 }
155
156 #[cfg(not(feature = "server"))]
159 unimplemented!("Lazy initialization is only supported with tokio and threads enabled.")
160}