zng_app_context/
context_local.rs

1use std::sync::Arc;
2
3use parking_lot::RwLock;
4
5use crate::{
6    AppLocal, AppLocalId, AppLocalImpl, LocalContext, LocalValueKind, ReadOnlyRwLock, RwLockReadGuardOwned, RwLockWriteGuardOwned,
7};
8
9#[doc(hidden)]
10pub struct ContextLocalData<T: Send + Sync + 'static> {
11    default_init: fn() -> T,
12    default_value: Option<Arc<T>>,
13}
14impl<T: Send + Sync + 'static> ContextLocalData<T> {
15    #[doc(hidden)]
16    pub const fn new(default_init: fn() -> T) -> Self {
17        Self {
18            default_init,
19            default_value: None,
20        }
21    }
22}
23
24/// Represents an [`AppLocal<T>`] value that can be temporarily overridden in a context.
25///
26/// The *context* works across threads, as long as the threads are instrumented using [`LocalContext`].
27///
28/// Use the [`context_local!`] macro to declare a static variable in the same style as [`thread_local!`].
29///
30/// [`context_local!`]: crate::context_local!
31pub struct ContextLocal<T: Send + Sync + 'static> {
32    data: AppLocal<ContextLocalData<T>>,
33}
34impl<T: Send + Sync + 'static> ContextLocal<T> {
35    #[doc(hidden)]
36    pub const fn new(storage: fn() -> &'static dyn AppLocalImpl<ContextLocalData<T>>) -> Self {
37        Self {
38            data: AppLocal::new(storage),
39        }
40    }
41
42    /// Gets an ID for this context local instance that is valid for the lifetime of the process.
43    ///
44    /// Note that comparing two `&'static CTX_LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
45    /// can be different and still represent the same app local. This ID identifies the actual inner pointer.
46    pub fn id(&'static self) -> AppLocalId {
47        self.data.id()
48    }
49
50    /// Calls `f` with the `value` loaded in context.
51    ///
52    /// The `value` is moved into context, `f` is called, then the value is moved back to `value`.
53    ///
54    /// # Panics
55    ///
56    /// Panics if `value` is `None`.
57    pub fn with_context<R>(&'static self, value: &mut Option<Arc<T>>, f: impl FnOnce() -> R) -> R {
58        let mut r = None;
59        let f = || r = Some(f());
60        #[cfg(feature = "dyn_closure")]
61        let f: Box<dyn FnOnce()> = Box::new(f);
62
63        LocalContext::with_value_ctx(self, LocalValueKind::Local, value, f);
64
65        r.unwrap()
66    }
67
68    /// Same as [`with_context`], but `value` represents a variable.
69    ///
70    /// Values loaded with this method are captured by [`CaptureFilter::ContextVars`].
71    ///
72    /// [`with_context`]: Self::with_context
73    /// [`CaptureFilter::ContextVars`]: crate::CaptureFilter::ContextVars
74    pub fn with_context_var<R>(&'static self, value: &mut Option<Arc<T>>, f: impl FnOnce() -> R) -> R {
75        let mut r = None;
76        let f = || r = Some(f());
77
78        #[cfg(feature = "dyn_closure")]
79        let f: Box<dyn FnOnce()> = Box::new(f);
80
81        LocalContext::with_value_ctx(self, LocalValueKind::Var, value, f);
82
83        r.unwrap()
84    }
85
86    /// Calls `f` with no value loaded in context.
87    pub fn with_default<R>(&'static self, f: impl FnOnce() -> R) -> R {
88        let mut r = None;
89        let f = || r = Some(f());
90
91        #[cfg(feature = "dyn_closure")]
92        let f: Box<dyn FnOnce()> = Box::new(f);
93        LocalContext::with_default_ctx(self, f);
94
95        r.unwrap()
96    }
97
98    /// Gets if no value is set in the context.
99    pub fn is_default(&'static self) -> bool {
100        !LocalContext::contains(self.id())
101    }
102
103    /// Clone a reference to the current value in the context or the default value.
104    pub fn get(&'static self) -> Arc<T> {
105        let cl = self.data.read();
106        match LocalContext::get(self.id()) {
107            Some(c) => Arc::downcast(c.0).unwrap(),
108            None => match &cl.default_value {
109                Some(d) => d.clone(),
110                None => {
111                    drop(cl);
112                    let mut cl = self.data.write();
113                    match &cl.default_value {
114                        None => {
115                            let d = Arc::new((cl.default_init)());
116                            cl.default_value = Some(d.clone());
117                            d
118                        }
119                        Some(d) => d.clone(),
120                    }
121                }
122            },
123        }
124    }
125
126    /// Clone the current value in the context or the default value.
127    pub fn get_clone(&'static self) -> T
128    where
129        T: Clone,
130    {
131        let cl = self.data.read();
132        match LocalContext::get(self.id()) {
133            Some(c) => c.0.downcast_ref::<T>().unwrap().clone(),
134            None => match &cl.default_value {
135                Some(d) => d.as_ref().clone(),
136                None => {
137                    drop(cl);
138                    let mut cl = self.data.write();
139                    match &cl.default_value {
140                        None => {
141                            let val = (cl.default_init)();
142                            let r = val.clone();
143                            cl.default_value = Some(Arc::new(val));
144                            r
145                        }
146                        Some(d) => d.as_ref().clone(),
147                    }
148                }
149            },
150        }
151    }
152}
153
154impl<T: Send + Sync + 'static> ContextLocal<RwLock<T>> {
155    /// Gets a read-only shared reference to the current context value.
156    pub fn read_only(&'static self) -> ReadOnlyRwLock<T> {
157        ReadOnlyRwLock::new(self.get())
158    }
159
160    /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
161    ///
162    /// See `parking_lot::RwLock::read` for more details.
163    pub fn read(&'static self) -> RwLockReadGuardOwned<T> {
164        RwLockReadGuardOwned::lock(self.get())
165    }
166
167    /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
168    ///
169    /// Unlike `read`, this method is guaranteed to succeed without blocking if
170    /// another read lock is held at the time of the call.
171    ///
172    /// See `parking_lot::RwLock::read` for more details.
173    pub fn read_recursive(&'static self) -> RwLockReadGuardOwned<T> {
174        RwLockReadGuardOwned::lock_recursive(self.get())
175    }
176
177    /// Locks this `RwLock` with exclusive write access, blocking the current
178    /// thread until it can be acquired.
179    ///
180    /// See `parking_lot::RwLock::write` for more details.
181    pub fn write(&'static self) -> RwLockWriteGuardOwned<T> {
182        RwLockWriteGuardOwned::lock(self.get())
183    }
184
185    /// Try lock this `RwLock` with shared read access, blocking the current thread until it can be acquired.
186    ///
187    /// See `parking_lot::RwLock::try_read` for more details.
188    pub fn try_read(&'static self) -> Option<RwLockReadGuardOwned<T>> {
189        RwLockReadGuardOwned::try_lock(self.get())
190    }
191
192    /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
193    ///
194    /// See `parking_lot::RwLock::try_read_recursive` for more details.
195    pub fn try_read_recursive(&'static self) -> Option<RwLockReadGuardOwned<T>> {
196        RwLockReadGuardOwned::try_lock_recursive(self.get())
197    }
198
199    /// Locks this `RwLock` with exclusive write access, blocking the current
200    /// thread until it can be acquired.
201    ///
202    /// See `parking_lot::RwLock::try_write` for more details.
203    pub fn try_write(&'static self) -> Option<RwLockWriteGuardOwned<T>> {
204        RwLockWriteGuardOwned::try_lock(self.get())
205    }
206}
207
208///<span data-del-macro-root></span> Declares new app and context local variable.
209///
210/// # Examples
211///
212/// ```
213/// # use zng_app_context::*;
214/// context_local! {
215///     /// A public documented value.
216///     pub static FOO: u8 = 10u8;
217///
218///     // A private value.
219///     static BAR: String = "Into!";
220/// }
221/// ```
222///
223/// # Default Value
224///
225/// All contextual values must have a fallback value that is used when no context is loaded.
226///
227/// The default value is instantiated once per app, the expression can be any static value that converts [`Into<T>`].
228///
229/// # Usage
230///
231/// After you declare the context local you can use it by loading a contextual value for the duration of a closure call.
232///
233/// ```
234/// # use zng_app_context::*;
235/// # use std::sync::Arc;
236/// context_local! { static FOO: String = "default"; }
237///
238/// fn print_value() {
239///     println!("value is {}!", FOO.get());
240/// }
241///
242/// let _scope = LocalContext::start_app(AppId::new_unique());
243///
244/// let mut value = Some(Arc::new(String::from("other")));
245/// FOO.with_context(&mut value, || {
246///     print!("in context, ");
247///     print_value();
248/// });
249///
250/// print!("out of context, ");
251/// print_value();
252/// ```
253///
254/// The example above prints:
255///
256/// ```text
257/// in context, value is other!
258/// out of context, value is default!
259/// ```
260///
261/// See [`ContextLocal<T>`] for more details.
262#[macro_export]
263macro_rules! context_local {
264    ($(
265        $(#[$meta:meta])*
266        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
267    )+) => {$(
268        $crate::context_local_impl! {
269            $(#[$meta])*
270            $vis static $IDENT: $T = $init;
271        }
272    )+};
273}
274
275#[doc(hidden)]
276#[macro_export]
277macro_rules! context_local_impl_single {
278    ($(
279        $(#[$meta:meta])*
280        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
281    )+) => {$(
282        $(#[$meta])*
283        $vis static $IDENT: $crate::ContextLocal<$T> = {
284            fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
285                fn init() -> $T {
286                    std::convert::Into::into($init)
287                }
288                $crate::hot_static! {
289                    static IMPL: $crate::AppLocalConst<$crate::ContextLocalData<$T>> =
290                    $crate::AppLocalConst::new(
291                        $crate::ContextLocalData::new(init)
292                    );
293                }
294                $crate::hot_static_ref!(IMPL)
295            }
296            $crate::ContextLocal::new(s)
297        };
298    )+};
299}
300
301#[doc(hidden)]
302#[macro_export]
303macro_rules! context_local_impl_multi {
304    ($(
305        $(#[$meta:meta])*
306        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
307    )+) => {$(
308        $(#[$meta])*
309        $vis static $IDENT: $crate::ContextLocal<$T> = {
310            fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
311                fn init() -> $T {
312                    std::convert::Into::into($init)
313                }
314                $crate::hot_static! {
315                    static IMPL: $crate::AppLocalVec<$crate::ContextLocalData<$T>> =
316                    $crate::AppLocalVec::new(
317                        || $crate::ContextLocalData::new(init)
318                    );
319                }
320                $crate::hot_static_ref!(IMPL)
321            }
322            $crate::ContextLocal::new(s)
323        };
324    )+};
325}
326
327#[cfg(feature = "multi_app")]
328#[doc(hidden)]
329pub use context_local_impl_multi as context_local_impl;
330
331#[cfg(not(feature = "multi_app"))]
332#[doc(hidden)]
333pub use context_local_impl_single as context_local_impl;