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;