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! {
237///     static FOO: String = "default";
238/// }
239///
240/// fn print_value() {
241///     println!("value is {}!", FOO.get());
242/// }
243///
244/// let _scope = LocalContext::start_app(AppId::new_unique());
245///
246/// let mut value = Some(Arc::new(String::from("other")));
247/// FOO.with_context(&mut value, || {
248///     print!("in context, ");
249///     print_value();
250/// });
251///
252/// print!("out of context, ");
253/// print_value();
254/// ```
255///
256/// The example above prints:
257///
258/// ```text
259/// in context, value is other!
260/// out of context, value is default!
261/// ```
262///
263/// See [`ContextLocal<T>`] for more details.
264#[macro_export]
265macro_rules! context_local {
266    ($(
267        $(#[$meta:meta])*
268        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
269    )+) => {$(
270        $crate::context_local_impl! {
271            $(#[$meta])*
272            $vis static $IDENT: $T = $init;
273        }
274    )+};
275}
276
277#[doc(hidden)]
278#[macro_export]
279macro_rules! context_local_impl_single {
280    ($(
281        $(#[$meta:meta])*
282        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
283    )+) => {$(
284        $(#[$meta])*
285        $vis static $IDENT: $crate::ContextLocal<$T> = {
286            fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
287                fn init() -> $T {
288                    std::convert::Into::into($init)
289                }
290                $crate::hot_static! {
291                    static IMPL: $crate::AppLocalConst<$crate::ContextLocalData<$T>> =
292                    $crate::AppLocalConst::new(
293                        $crate::ContextLocalData::new(init)
294                    );
295                }
296                $crate::hot_static_ref!(IMPL)
297            }
298            $crate::ContextLocal::new(s)
299        };
300    )+};
301}
302
303#[doc(hidden)]
304#[macro_export]
305macro_rules! context_local_impl_multi {
306    ($(
307        $(#[$meta:meta])*
308        $vis:vis static $IDENT:ident : $T:ty = $init:expr;
309    )+) => {$(
310        $(#[$meta])*
311        $vis static $IDENT: $crate::ContextLocal<$T> = {
312            fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
313                fn init() -> $T {
314                    std::convert::Into::into($init)
315                }
316                $crate::hot_static! {
317                    static IMPL: $crate::AppLocalVec<$crate::ContextLocalData<$T>> =
318                    $crate::AppLocalVec::new(
319                        || $crate::ContextLocalData::new(init)
320                    );
321                }
322                $crate::hot_static_ref!(IMPL)
323            }
324            $crate::ContextLocal::new(s)
325        };
326    )+};
327}
328
329#[cfg(feature = "multi_app")]
330#[doc(hidden)]
331pub use context_local_impl_multi as context_local_impl;
332
333#[cfg(not(feature = "multi_app"))]
334#[doc(hidden)]
335pub use context_local_impl_single as context_local_impl;