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            // #[linkme::distributed_slice(HOT_STATICS)]
74            // static _HOT_STATICS: fn(&FooArgs) = _foo;
75            // so that users don't need to depend on linkme just to call this macro.
76            #[used]
77            #[cfg_attr(
78                any(
79                    target_os = "none",
80                    target_os = "linux",
81                    target_os = "android",
82                    target_os = "fuchsia",
83                    target_os = "psp"
84                ),
85                unsafe(link_section = "linkme_HOT_STATICS")
86            )]
87            #[cfg_attr(
88                any(target_os = "macos", target_os = "ios", target_os = "tvos"),
89                unsafe(link_section = "__DATA,__linkmeAGDMMOwP,regular,no_dead_strip")
90            )]
91            #[cfg_attr(any(target_os = "uefi", target_os = "windows"), unsafe(link_section = ".linkme_HOT_STATICS$b"))]
92            #[cfg_attr(target_os = "illumos", unsafe(link_section = "set_linkme_HOT_STATICS"))]
93            #[cfg_attr(any(target_os = "freebsd", target_os = "openbsd"), unsafe(link_section = "linkme_HOT_STATICS"))]
94            #[doc(hidden)]
95            static [<$IDENT _REGISTER>]: (&'static dyn $crate::hot_reload::PatchKey, unsafe fn(*const ()) -> *const ()) = (
96                &[<_K $IDENT:camel>],
97                [<$IDENT _INIT>]
98            );
99
100        }
101    };
102}
103
104#[doc(hidden)]
105pub unsafe fn init_static<T>(s: &mut &'static T, static_ptr: *const ()) -> *const () {
106    if static_ptr.is_null() {
107        *s as *const T as *const ()
108    } else {
109        *s = unsafe { &*(static_ptr as *const T) };
110        std::ptr::null()
111    }
112}
113
114use std::{any::Any, fmt, ops};
115
116#[doc(hidden)]
117#[cfg(not(feature = "hot_reload"))]
118pub use crate::hot_static_not_patchable as hot_static_impl;
119
120#[doc(hidden)]
121#[cfg(feature = "hot_reload")]
122pub use crate::hot_static_patchable as hot_static_impl;
123
124#[doc(hidden)]
125#[macro_export]
126macro_rules! hot_static_ref_not_patchable {
127    ($PATH:path) => {
128        &$PATH
129    };
130}
131
132#[doc(hidden)]
133#[macro_export]
134macro_rules! hot_static_ref_patchable {
135    ($PATH:path) => {
136        // SAFETY: hot_static does not mutate after dylib init.
137        unsafe { $PATH }
138    };
139}
140
141#[doc(hidden)]
142#[cfg(not(feature = "hot_reload"))]
143pub use crate::hot_static_ref_not_patchable as hot_static_ref_impl;
144
145#[doc(hidden)]
146#[cfg(feature = "hot_reload")]
147pub use crate::hot_static_ref_patchable as hot_static_ref_impl;
148
149#[doc(hidden)]
150#[cfg(feature = "hot_reload")]
151#[linkme::distributed_slice]
152pub static HOT_STATICS: [(&'static dyn PatchKey, unsafe fn(*const ()) -> *const ())];
153
154#[doc(hidden)]
155pub trait PatchKey: Send + Sync + Any {
156    fn id(&'static self) -> &'static str;
157}
158impl PartialEq for &'static dyn PatchKey {
159    fn eq(&self, other: &Self) -> bool {
160        self.id() == other.id()
161    }
162}
163impl Eq for &'static dyn PatchKey {}
164impl std::hash::Hash for &'static dyn PatchKey {
165    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
166        std::hash::Hash::hash(self.id(), state)
167    }
168}
169impl fmt::Debug for &'static dyn PatchKey {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        fmt::Debug::fmt(&self.id(), f)
172    }
173}
174
175#[doc(hidden)]
176pub use once_cell::sync::OnceCell as OnceCellLazy;
177
178#[doc(hidden)]
179pub struct Lazy<T: 'static> {
180    #[cfg(feature = "hot_reload")]
181    inner: fn(&mut Option<T>) -> &'static T,
182    #[cfg(not(feature = "hot_reload"))]
183    inner: (OnceCellLazy<T>, fn() -> T),
184}
185
186impl<T: 'static> Lazy<T> {
187    #[doc(hidden)]
188    #[cfg(feature = "hot_reload")]
189    pub const fn new(inner: fn(&mut Option<T>) -> &'static T) -> Self {
190        Self { inner }
191    }
192
193    #[doc(hidden)]
194    #[cfg(not(feature = "hot_reload"))]
195    pub const fn new(init: fn() -> T) -> Self {
196        Self {
197            inner: (OnceCellLazy::new(), init),
198        }
199    }
200}
201impl<T: 'static> ops::Deref for Lazy<T> {
202    type Target = T;
203
204    #[cfg(feature = "hot_reload")]
205    fn deref(&self) -> &Self::Target {
206        (self.inner)(&mut None)
207    }
208
209    #[cfg(not(feature = "hot_reload"))]
210    fn deref(&self) -> &Self::Target {
211        self.inner.0.get_or_init(|| (self.inner.1)())
212    }
213}
214
215/// Initializes a [`lazy_static!`] with a custom value if it is not yet inited.
216///
217/// [`lazy_static!`]: crate::lazy_static
218pub fn lazy_static_init<T>(lazy_static: &'static Lazy<T>, value: T) -> Result<&'static T, T> {
219    let mut value = Some(value);
220
221    #[cfg(feature = "hot_reload")]
222    let r = (lazy_static.inner)(&mut value);
223    #[cfg(not(feature = "hot_reload"))]
224    let r = {
225        let (lazy, _) = &lazy_static.inner;
226        lazy.get_or_init(|| value.take().unwrap())
227    };
228
229    match value {
230        Some(v) => Err(v),
231        None => Ok(r),
232    }
233}
234
235#[doc(hidden)]
236#[cfg(feature = "hot_reload")]
237pub fn lazy_static_ref<T>(lazy_static: &'static OnceCellLazy<T>, init: fn() -> T, override_init: &mut Option<T>) -> &'static T {
238    lazy_static.get_or_init(|| match override_init.take() {
239        Some(o) => o,
240        None => init(),
241    })
242}
243
244/// Implementation of `lazy_static!` that supports hot reloading.
245///
246/// The syntax is similar to the [`lazy_static`](https://docs.rs/lazy_static) crate,
247/// but the generated code uses the [`once_cell::sync::Lazy`](https://docs.rs/once_cell/once_cell/sync/struct.Lazy.html)
248/// type internally.
249#[macro_export]
250macro_rules! lazy_static {
251    ($(
252        $(#[$attr:meta])*
253        $vis:vis static ref $N:ident : $T:ty = $e:expr;
254    )+) => {
255        $(
256           $crate::hot_reload::lazy_static_impl! {
257                $(#[$attr])*
258                $vis static ref $N : $T = $e;
259           }
260        )+
261    };
262}
263
264#[doc(hidden)]
265#[macro_export]
266macro_rules! lazy_static_patchable {
267    (
268        $(#[$attr:meta])*
269        $vis:vis static ref $N:ident : $T:ty = $e:expr;
270    ) => {
271        $crate::paste! {
272            fn [<_ $N:lower _hot>](__override: &mut Option<$T>) -> &'static $T {
273                fn __init() -> $T {
274                    $e
275                }
276                $crate::hot_static! {
277                    static IMPL: $crate::hot_reload::OnceCellLazy<$T> = $crate::hot_reload::OnceCellLazy::new();
278                }
279                $crate::hot_reload::lazy_static_ref($crate::hot_static_ref!(IMPL), __init, __override)
280            }
281
282            $(#[$attr])*
283            $vis static $N: $crate::hot_reload::Lazy<$T> = $crate::hot_reload::Lazy::new([<_ $N:lower _hot>]);
284        }
285    };
286}
287
288#[doc(hidden)]
289#[macro_export]
290macro_rules! lazy_static_not_patchable {
291    (
292        $(#[$attr:meta])*
293        $vis:vis static ref $N:ident : $T:ty = $e:expr;
294    ) => {
295        $crate::paste! {
296            fn [<_ $N:lower _init>]() -> $T {
297                $e
298            }
299
300            $(#[$attr])*
301            $vis static $N: $crate::hot_reload::Lazy<$T> = $crate::hot_reload::Lazy::new([<_ $N:lower _init>]);
302        }
303    };
304}
305
306#[doc(hidden)]
307#[cfg(not(feature = "hot_reload"))]
308pub use crate::lazy_static_not_patchable as lazy_static_impl;
309
310#[doc(hidden)]
311#[cfg(feature = "hot_reload")]
312pub use crate::lazy_static_patchable as lazy_static_impl;