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!(
44 "The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables."
45 );
46 }
47
48 black_box(constructor);
50
51 Self {
52 _phantom: std::marker::PhantomData,
53 value: std::sync::OnceLock::new(),
54 started_initialization: AtomicBool::new(false),
55 constructor: Some(blocking_initialize::<T, F, G, E>),
56 }
57 }
58
59 pub fn set(&self, pool: T) -> Result<(), CapturedError> {
64 let res = self.value.set(pool);
65 if res.is_err() {
66 return Err(anyhow::anyhow!("Lazy value is already initialized.").into());
67 }
68
69 Ok(())
70 }
71
72 pub fn try_set(&self, pool: T) -> Result<(), T> {
73 self.value.set(pool)
74 }
75
76 pub fn initialize(&self) -> Result<(), CapturedError> {
78 if let Some(constructor) = self.constructor {
79 if self
81 .started_initialization
82 .swap(true, std::sync::atomic::Ordering::SeqCst)
83 {
84 self.value.wait();
85 return Ok(());
86 }
87
88 self.set(constructor().unwrap())?;
90 }
91 Ok(())
92 }
93
94 pub fn get(&self) -> &T {
97 if self.constructor.is_none() {
98 return self.value.get().expect("Lazy value is not initialized. Make sure to call `initialize` before dereferencing.");
99 };
100
101 if self.value.get().is_none() {
102 self.initialize().expect("Failed to initialize lazy value");
103 }
104
105 self.value.get().unwrap()
106 }
107}
108
109impl<T: Send + Sync + 'static> Default for Lazy<T> {
110 fn default() -> Self {
111 Self::lazy()
112 }
113}
114
115impl<T: Send + Sync + 'static> std::ops::Deref for Lazy<T> {
116 type Target = T;
117
118 fn deref(&self) -> &Self::Target {
119 self.get()
120 }
121}
122
123impl<T: std::fmt::Debug + Send + Sync + 'static> std::fmt::Debug for Lazy<T> {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 f.debug_struct("Lazy").field("value", self.get()).finish()
126 }
127}
128
129fn blocking_initialize<T, F, G, E>() -> Result<T, CapturedError>
134where
135 T: Send + Sync + 'static,
136 F: Fn() -> G + Copy,
137 G: Future<Output = Result<T, E>> + Send + 'static,
138 E: Into<CapturedError>,
139{
140 assert_eq!(
141 std::mem::size_of::<F>(),
142 0,
143 "The constructor function must be a zero-sized type (ZST). Consider using a function pointer or a closure without captured variables."
144 );
145
146 #[cfg(feature = "server")]
147 {
148 let ptr: F = unsafe { std::mem::zeroed() };
149 let fut = ptr();
150 return std::thread::spawn(move || {
151 tokio::runtime::Builder::new_current_thread()
152 .enable_all()
153 .build()
154 .unwrap()
155 .block_on(fut)
156 .map_err(|e| e.into())
157 })
158 .join()
159 .unwrap();
160 }
161
162 #[cfg(not(feature = "server"))]
165 unimplemented!("Lazy initialization is only supported with tokio and threads enabled.")
166}