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