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;