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