zng_unique_id/
hot_reload.rs

1// # Full API
2//
3// The full API is implemented in `zng-ext-hot-reload`, `HOT_STATICS` and `hot_static` are declared
4// in this crate to only to be reachable in more workspace crates.
5
6/// Declares a patchable static.
7///
8/// In builds with Cargo feature `hot_reload` this generates an unsafe static double reference that can be addressed by name and
9/// patched in a dynamically loaded build of the exact same crate.
10///
11/// Use [`hot_static_ref!`] to safely reference the static, attempting to access the variable in any other way is undefined behavior.
12///
13/// Note that you can only declare private static items, this is by design, you can share the [`hot_static_ref!`] output at
14/// a higher visibility.
15///
16/// See `zng::hot_reload` for more details and links to the full API. This macro is declared on the `zng-unique-id` crate
17/// only to avoid circular dependencies in the Zng workspace.
18///
19/// [`hot_static_ref!`]: crate::hot_static_ref!
20#[macro_export]
21macro_rules! hot_static {
22    (
23        static $IDENT:ident: $Ty:ty = $init:expr;
24    ) => {
25        $crate::hot_reload::hot_static_impl! {
26            static $IDENT: $Ty = $init;
27        }
28    };
29}
30
31/// Static reference to a [`hot_static!`].
32///
33/// [`hot_static!`]: crate::hot_static!
34#[macro_export]
35macro_rules! hot_static_ref {
36    ($PATH:path) => {
37        $crate::hot_reload::hot_static_ref_impl!($PATH)
38    };
39}
40
41#[doc(hidden)]
42#[macro_export]
43macro_rules! hot_static_not_patchable {
44    (
45        static $IDENT:ident: $Ty:ty = $init:expr;
46    ) => {
47        static $IDENT: $Ty = $init;
48    };
49}
50#[doc(hidden)]
51#[macro_export]
52macro_rules! hot_static_patchable {
53    (
54        $vis:vis static $IDENT:ident: $Ty:ty = $init:expr;
55    ) => {
56        $crate::paste! {
57            struct [<_K $IDENT:camel>];
58            impl $crate::hot_reload::PatchKey for [<_K $IDENT:camel>] {
59                fn id(&'static self) -> &'static str {
60                    std::any::type_name::<[<_K $IDENT:camel>]>()
61                }
62            }
63
64            static [<$IDENT _COLD>] : $Ty = $init;
65            #[allow(non_camel_case_types)]
66            static mut $IDENT: &$Ty = &[<$IDENT _COLD>];
67            #[allow(non_snake_case)]
68            unsafe fn [<$IDENT _INIT>](static_ptr: *const ()) -> *const () {
69                unsafe { $crate::hot_reload::init_static(&mut $IDENT, static_ptr) }
70            }
71
72            // expanded from:
73            #[$crate::hot_reload::__linkme::distributed_slice($crate::hot_reload::HOT_STATICS)]
74            #[linkme(crate=$crate::hot_reload::__linkme)]
75            #[doc(hidden)]
76            static [<$IDENT _REGISTER>]: (&'static dyn $crate::hot_reload::PatchKey, unsafe fn(*const ()) -> *const ()) = (
77                &[<_K $IDENT:camel>],
78                [<$IDENT _INIT>]
79            );
80
81        }
82    };
83}
84
85#[doc(hidden)]
86pub unsafe fn init_static<T>(s: &mut &'static T, static_ptr: *const ()) -> *const () {
87    if static_ptr.is_null() {
88        *s as *const T as *const ()
89    } else {
90        *s = unsafe { &*(static_ptr as *const T) };
91        std::ptr::null()
92    }
93}
94
95use std::{any::Any, fmt, ops};
96
97#[doc(hidden)]
98#[cfg(feature = "hot_reload")]
99pub use linkme as __linkme;
100
101#[doc(hidden)]
102#[cfg(not(feature = "hot_reload"))]
103pub use crate::hot_static_not_patchable as hot_static_impl;
104
105#[doc(hidden)]
106#[cfg(feature = "hot_reload")]
107pub use crate::hot_static_patchable as hot_static_impl;
108
109#[doc(hidden)]
110#[macro_export]
111macro_rules! hot_static_ref_not_patchable {
112    ($PATH:path) => {
113        &$PATH
114    };
115}
116
117#[doc(hidden)]
118#[macro_export]
119macro_rules! hot_static_ref_patchable {
120    ($PATH:path) => {
121        // SAFETY: hot_static does not mutate after dylib init.
122        unsafe { $PATH }
123    };
124}
125
126#[doc(hidden)]
127#[cfg(not(feature = "hot_reload"))]
128pub use crate::hot_static_ref_not_patchable as hot_static_ref_impl;
129
130#[doc(hidden)]
131#[cfg(feature = "hot_reload")]
132pub use crate::hot_static_ref_patchable as hot_static_ref_impl;
133
134#[doc(hidden)]
135#[cfg(feature = "hot_reload")]
136#[linkme::distributed_slice]
137pub static HOT_STATICS: [(&'static dyn PatchKey, unsafe fn(*const ()) -> *const ())];
138
139#[doc(hidden)]
140pub trait PatchKey: Send + Sync + Any {
141    fn id(&'static self) -> &'static str;
142}
143impl PartialEq for &'static dyn PatchKey {
144    fn eq(&self, other: &Self) -> bool {
145        self.id() == other.id()
146    }
147}
148impl Eq for &'static dyn PatchKey {}
149impl std::hash::Hash for &'static dyn PatchKey {
150    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
151        std::hash::Hash::hash(self.id(), state)
152    }
153}
154impl fmt::Debug for &'static dyn PatchKey {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        fmt::Debug::fmt(&self.id(), f)
157    }
158}
159
160#[doc(hidden)]
161pub use once_cell::sync::OnceCell as OnceCellLazy;
162
163#[doc(hidden)]
164pub struct Lazy<T: 'static> {
165    #[cfg(feature = "hot_reload")]
166    inner: fn(&mut Option<T>) -> &'static T,
167    #[cfg(not(feature = "hot_reload"))]
168    inner: (OnceCellLazy<T>, fn() -> T),
169}
170
171impl<T: 'static> Lazy<T> {
172    #[doc(hidden)]
173    #[cfg(feature = "hot_reload")]
174    pub const fn new(inner: fn(&mut Option<T>) -> &'static T) -> Self {
175        Self { inner }
176    }
177
178    #[doc(hidden)]
179    #[cfg(not(feature = "hot_reload"))]
180    pub const fn new(init: fn() -> T) -> Self {
181        Self {
182            inner: (OnceCellLazy::new(), init),
183        }
184    }
185}
186impl<T: 'static> ops::Deref for Lazy<T> {
187    type Target = T;
188
189    #[cfg(feature = "hot_reload")]
190    fn deref(&self) -> &Self::Target {
191        (self.inner)(&mut None)
192    }
193
194    #[cfg(not(feature = "hot_reload"))]
195    fn deref(&self) -> &Self::Target {
196        self.inner.0.get_or_init(|| (self.inner.1)())
197    }
198}
199
200/// Initializes a [`lazy_static!`] with a custom value if it is not yet inited.
201///
202/// [`lazy_static!`]: crate::lazy_static
203pub fn lazy_static_init<T>(lazy_static: &'static Lazy<T>, value: T) -> Result<&'static T, T> {
204    let mut value = Some(value);
205
206    #[cfg(feature = "hot_reload")]
207    let r = (lazy_static.inner)(&mut value);
208    #[cfg(not(feature = "hot_reload"))]
209    let r = {
210        let (lazy, _) = &lazy_static.inner;
211        lazy.get_or_init(|| value.take().unwrap())
212    };
213
214    match value {
215        Some(v) => Err(v),
216        None => Ok(r),
217    }
218}
219
220#[doc(hidden)]
221#[cfg(feature = "hot_reload")]
222pub fn lazy_static_ref<T>(lazy_static: &'static OnceCellLazy<T>, init: fn() -> T, override_init: &mut Option<T>) -> &'static T {
223    lazy_static.get_or_init(|| match override_init.take() {
224        Some(o) => o,
225        None => init(),
226    })
227}
228
229/// Implementation of `lazy_static!` that supports hot reloading.
230///
231/// The syntax is similar to the [`lazy_static`](https://docs.rs/lazy_static) crate,
232/// but the generated code uses the [`once_cell::sync::Lazy`](https://docs.rs/once_cell/once_cell/sync/struct.Lazy.html)
233/// type internally.
234#[macro_export]
235macro_rules! lazy_static {
236    ($(
237        $(#[$attr:meta])*
238        $vis:vis static ref $N:ident : $T:ty = $e:expr;
239    )+) => {
240        $(
241           $crate::hot_reload::lazy_static_impl! {
242                $(#[$attr])*
243                $vis static ref $N : $T = $e;
244           }
245        )+
246    };
247}
248
249#[doc(hidden)]
250#[macro_export]
251macro_rules! lazy_static_patchable {
252    (
253        $(#[$attr:meta])*
254        $vis:vis static ref $N:ident : $T:ty = $e:expr;
255    ) => {
256        $crate::paste! {
257            fn [<_ $N:lower _hot>](__override: &mut Option<$T>) -> &'static $T {
258                fn __init() -> $T {
259                    $e
260                }
261                $crate::hot_static! {
262                    static IMPL: $crate::hot_reload::OnceCellLazy<$T> = $crate::hot_reload::OnceCellLazy::new();
263                }
264                $crate::hot_reload::lazy_static_ref($crate::hot_static_ref!(IMPL), __init, __override)
265            }
266
267            $(#[$attr])*
268            $vis static $N: $crate::hot_reload::Lazy<$T> = $crate::hot_reload::Lazy::new([<_ $N:lower _hot>]);
269        }
270    };
271}
272
273#[doc(hidden)]
274#[macro_export]
275macro_rules! lazy_static_not_patchable {
276    (
277        $(#[$attr:meta])*
278        $vis:vis static ref $N:ident : $T:ty = $e:expr;
279    ) => {
280        $crate::paste! {
281            fn [<_ $N:lower _init>]() -> $T {
282                $e
283            }
284
285            $(#[$attr])*
286            $vis static $N: $crate::hot_reload::Lazy<$T> = $crate::hot_reload::Lazy::new([<_ $N:lower _init>]);
287        }
288    };
289}
290
291#[doc(hidden)]
292#[cfg(not(feature = "hot_reload"))]
293pub use crate::lazy_static_not_patchable as lazy_static_impl;
294
295#[doc(hidden)]
296#[cfg(feature = "hot_reload")]
297pub use crate::lazy_static_patchable as lazy_static_impl;