fluid_let/lib.rs
1// Copyright (c) 2019, ilammy
2// Licensed under MIT license (see LICENSE)
3
4//! Dynamically scoped variables.
5//!
6//! _Dynamic_ or _fluid_ variables are a handy way to define global configuration values.
7//! They come from the Lisp family of languages where they are relatively popular in this role.
8//!
9//! # Declaring dynamic variables
10//!
11//! [`fluid_let!`] macro is used to declare dynamic variables. Dynamic variables
12//! are _global_, therefore they must be declared as `static`:
13//!
14//! [`fluid_let!`]: macro.fluid_let.html
15//!
16//! ```
17//! use std::fs::File;
18//!
19//! use fluid_let::fluid_let;
20//!
21//! fluid_let!(static LOG_FILE: File);
22//! ```
23//!
24//! The actual type of `LOG_FILE` variable is `Option<&File>`: that is,
25//! possibly absent reference to a file. All dynamic variables have `None` as
26//! their default value, unless a particular value is set for them.
27//!
28//! If you enable the [`"static-init"` feature](#features), it is also
29//! possible to provide `'static` initialization for types that allow it:
30//!
31//! ```no_run
32//! # use fluid_let::fluid_let;
33//! #
34//! # enum LogLevel { Info }
35//! #
36//! # #[cfg(feature = "static-init")]
37//! fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);
38//! ```
39//!
40//! Here `LOG_LEVEL` has `Some(&LogLevel::Info)` as its default value.
41//!
42//! # Setting dynamic variables
43//!
44//! [`set`] is used to give value to a dynamic variable:
45//!
46//! [`set`]: struct.DynamicVariable.html#method.set
47//!
48//! ```no_run
49//! # use std::fs::File;
50//! #
51//! # use fluid_let::fluid_let;
52//! #
53//! # fluid_let!(static LOG_FILE: File);
54//! #
55//! # fn open(path: &str) -> File { unimplemented!() }
56//! #
57//! let log_file: File = open("/tmp/log.txt");
58//!
59//! LOG_FILE.set(&log_file, || {
60//! //
61//! // logs will be redirected to /tmp/log.txt in this block
62//! //
63//! });
64//! ```
65//!
66//! Note that you store an _immutable reference_ in the dynamic variable.
67//! You can’t directly modify the dynamic variable value after setting it,
68//! but you can use something like `Cell` or `RefCell` to circumvent that.
69//!
70//! The new value is in effect within the _dynamic extent_ of the assignment,
71//! that is within the closure passed to `set`. Once the closure returns, the
72//! previous value of the variable is restored.
73//!
74//! If you do not need precise control over the extent of the assignment, you
75//! can use the [`fluid_set!`] macro to assign until the end of the scope:
76//!
77//! [`fluid_set!`]: macro.fluid_set.html
78//!
79//! ```no_run
80//! # use std::fs::File;
81//! #
82//! # use fluid_let::fluid_let;
83//! #
84//! # fluid_let!(static LOG_FILE: File);
85//! #
86//! # fn open(path: &str) -> File { unimplemented!() }
87//! #
88//! use fluid_let::fluid_set;
89//!
90//! fn chatterbox_function() {
91//! fluid_set!(LOG_FILE, open("/dev/null"));
92//! //
93//! // logs will be written to /dev/null in this function
94//! //
95//! }
96//! ```
97//!
98//! Obviously, you can also nest assignments arbitrarily:
99//!
100//! ```no_run
101//! # use std::fs::File;
102//! #
103//! # use fluid_let::{fluid_let, fluid_set};
104//! #
105//! # fluid_let!(static LOG_FILE: File);
106//! #
107//! # fn open(path: &str) -> File { unimplemented!() }
108//! #
109//! LOG_FILE.set(open("A.txt"), || {
110//! // log to A.txt here
111//! LOG_FILE.set(open("/dev/null"), || {
112//! // log to /dev/null for a bit
113//! fluid_set!(LOG_FILE, open("B.txt"));
114//! // log to B.txt starting with this line
115//! {
116//! fluid_set!(LOG_FILE, open("C.txt"));
117//! // but in this block log to C.txt
118//! }
119//! // before going back to using B.txt here
120//! });
121//! // and logging to A.txt again
122//! });
123//! ```
124//!
125//! # Accessing dynamic variables
126//!
127//! [`get`] is used to retrieve the current value of a dynamic variable:
128//!
129//! [`get`]: struct.DynamicVariable.html#method.get
130//!
131//! ```no_run
132//! # use std::io::{self, Write};
133//! # use std::fs::File;
134//! #
135//! # use fluid_let::fluid_let;
136//! #
137//! # fluid_let!(static LOG_FILE: File);
138//! #
139//! fn write_log(msg: &str) -> io::Result<()> {
140//! LOG_FILE.get(|current| {
141//! if let Some(mut log_file) = current {
142//! write!(log_file, "{}\n", msg)?;
143//! }
144//! Ok(())
145//! })
146//! }
147//! ```
148//!
149//! Current value of the dynamic variable is passed to the provided closure, and
150//! the value returned by the closure becomes the value of the `get()` call.
151//!
152//! This somewhat weird access interface is dictated by safety requirements. The
153//! dynamic variable itself is global and thus has `'static` lifetime. However,
154//! its values usually have shorter lifetimes, as short as the corresponing
155//! `set()` call. Therefore, access reference must have _even shorter_ lifetime.
156//!
157//! If the variable type implements `Clone` or `Copy` then you can use [`cloned`]
158//! and [`copied`] convenience accessors to get a copy of the current value:
159//!
160//! [`cloned`]: struct.DynamicVariable.html#method.cloned
161//! [`copied`]: struct.DynamicVariable.html#method.copied
162//!
163//! ```no_run
164//! # use std::io::{self, Write};
165//! # use std::fs::File;
166//! #
167//! # use fluid_let::fluid_let;
168//! #
169//! # fluid_let!(static LOG_FILE: File);
170//! #
171//! #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
172//! enum LogLevel {
173//! Debug,
174//! Info,
175//! Error,
176//! }
177//!
178//! # #[cfg(not(feature = "fluid-let"))]
179//! # fluid_let!(static LOG_LEVEL: LogLevel);
180//! # #[cfg(feature = "fluid-let")]
181//! fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);
182//!
183//! fn write_log(level: LogLevel, msg: &str) -> io::Result<()> {
184//! if level < LOG_LEVEL.copied().unwrap() {
185//! return Ok(());
186//! }
187//! LOG_FILE.get(|current| {
188//! if let Some(mut log_file) = current {
189//! write!(log_file, "{}\n", msg)?;
190//! }
191//! Ok(())
192//! })
193//! }
194//! ```
195//!
196//! # Thread safety
197//!
198//! Dynamic variables are global and _thread-local_. That is, each thread gets
199//! its own independent instance of a dynamic variable. Values set in one thread
200//! are visible only in this thread. Other threads will not see any changes in
201//! values of their dynamic variables and may have different configurations.
202//!
203//! Note, however, that this does not free you from the usual synchronization
204//! concerns when shared objects are involved. Dynamic variables hold _references_
205//! to objects. Therefore it is entirely possible to bind _the same_ object with
206//! internal mutability to a dynamic variable and access it from multiple threads.
207//! In this case you will probably need some synchronization to use the shared
208//! object in a safe manner, just like you would do when using `Arc` and friends.
209//!
210//! # Features
211//!
212//! Currently, there is only one optional feature: `"static-init"`,
213//! gating static initialization of dynamic variables:
214//!
215//! ```
216//! # use fluid_let::fluid_let;
217//! #
218//! # enum LogLevel { Info }
219//! #
220//! # #[cfg(feature = "static-init")]
221//! fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);
222//! // ~~~~~~~~~~~~~~~~
223//! ```
224//!
225//! The API for accessing known-initialized variables has not stabilized yet
226//! and may be subject to changes.
227
228use std::borrow::Borrow;
229use std::cell::UnsafeCell;
230use std::mem;
231use std::thread::LocalKey;
232
233#[cfg(feature = "static-init")]
234/// Declares global dynamic variables.
235///
236/// # Examples
237///
238/// One-line form for single declarations:
239///
240/// ```
241/// # use fluid_let::fluid_let;
242/// fluid_let!(static ENABLED: bool);
243/// ```
244///
245/// If [`"static-init"` feature](index.html#features) is enabled,
246/// you can provide initial value:
247///
248/// ```
249/// # use fluid_let::fluid_let;
250/// fluid_let!(static ENABLED: bool = true);
251/// ```
252///
253/// Multiple declarations with attributes and visibility modifiers are also supported:
254///
255/// ```
256/// # use fluid_let::fluid_let;
257/// fluid_let! {
258/// /// Length of `Debug` representation of hashes in characters.
259/// pub static HASH_LENGTH: usize = 32;
260///
261/// /// If set to true then passwords will be printed to logs.
262/// #[cfg(test)]
263/// static DUMP_PASSWORDS: bool;
264/// }
265/// ```
266///
267/// See also [crate-level documentation](index.html) for usage examples.
268#[macro_export]
269macro_rules! fluid_let {
270 // Simple case: a single definition with None value.
271 {
272 $(#[$attr:meta])*
273 $pub:vis static $name:ident: $type:ty
274 } => {
275 $(#[$attr])*
276 $pub static $name: $crate::DynamicVariable<$type> = {
277 thread_local! {
278 static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::empty();
279 }
280 $crate::DynamicVariable::new(&VARIABLE)
281 };
282 };
283 // Simple case: a single definition with Some value.
284 {
285 $(#[$attr:meta])*
286 $pub:vis static $name:ident: $type:ty = $value:expr
287 } => {
288 $(#[$attr])*
289 $pub static $name: $crate::DynamicVariable<$type> = {
290 static DEFAULT: $type = $value;
291 thread_local! {
292 static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::with_static(&DEFAULT);
293 }
294 $crate::DynamicVariable::new(&VARIABLE)
295 };
296 };
297 // Multiple definitions (iteration), with None value.
298 {
299 $(#[$attr:meta])*
300 $pub:vis static $name:ident: $type:ty;
301 $($rest:tt)*
302 } => {
303 $crate::fluid_let!($(#[$attr])* $pub static $name: $type);
304 $crate::fluid_let!($($rest)*);
305 };
306 // Multiple definitions (iteration), with Some value.
307 {
308 $(#[$attr:meta])*
309 $pub:vis static $name:ident: $type:ty = $value:expr;
310 $($rest:tt)*
311 } => {
312 $crate::fluid_let!($(#[$attr])* $pub static $name: $type = $value);
313 $crate::fluid_let!($($rest)*);
314 };
315 // No definitions (recursion base).
316 {} => {};
317}
318
319// FIXME(ilammy, 2021-10-12): Make "static-init" available by default
320//
321// Macros can't abstract out #[cfg(...)] checks in expanded code
322// thus we have to duplicate this macro to insert a compiler error.
323
324#[cfg(not(feature = "static-init"))]
325/// Declares global dynamic variables.
326///
327/// # Examples
328///
329/// One-line form for single declarations:
330///
331/// ```
332/// # use fluid_let::fluid_let;
333/// fluid_let!(static ENABLED: bool);
334/// ```
335///
336/// Multiple declarations with attributes and visibility modifiers are also supported:
337///
338/// ```
339/// # use fluid_let::fluid_let;
340/// fluid_let! {
341/// /// Length of `Debug` representation of hashes in characters.
342/// pub static HASH_LENGTH: usize;
343///
344/// /// If set to true then passwords will be printed to logs.
345/// #[cfg(test)]
346/// static DUMP_PASSWORDS: bool;
347/// }
348/// ```
349///
350/// See also [crate-level documentation](index.html) for usage examples.
351#[macro_export]
352macro_rules! fluid_let {
353 // Simple case: a single definition with None value.
354 {
355 $(#[$attr:meta])*
356 $pub:vis static $name:ident: $type:ty
357 } => {
358 $(#[$attr])*
359 $pub static $name: $crate::DynamicVariable<$type> = {
360 thread_local! {
361 static VARIABLE: $crate::DynamicCell<$type> = $crate::DynamicCell::empty();
362 }
363 $crate::DynamicVariable::new(&VARIABLE)
364 };
365 };
366 // Simple case: a single definition with Some value.
367 {
368 $(#[$attr:meta])*
369 $pub:vis static $name:ident: $type:ty = $value:expr
370 } => {
371 compile_error!("Static initialization is unstable, use \"static-init\" feature to opt-in");
372 };
373 // Multiple definitions (iteration), with None value.
374 {
375 $(#[$attr:meta])*
376 $pub:vis static $name:ident: $type:ty;
377 $($rest:tt)*
378 } => {
379 $crate::fluid_let!($(#[$attr])* $pub static $name: $type);
380 $crate::fluid_let!($($rest)*);
381 };
382 // Multiple definitions (iteration), with Some value.
383 {
384 $(#[$attr:meta])*
385 $pub:vis static $name:ident: $type:ty = $value:expr;
386 $($rest:tt)*
387 } => {
388 $crate::fluid_let!($(#[$attr])* $pub static $name: $type = $value);
389 $crate::fluid_let!($($rest)*);
390 };
391 // No definitions (recursion base).
392 {} => {};
393}
394
395/// Binds a value to a dynamic variable.
396///
397/// # Examples
398///
399/// If you do not need to explicitly delimit the scope of dynamic assignment then you can
400/// use `fluid_set!` to assign a value until the end of the current scope:
401///
402/// ```no_run
403/// use fluid_let::{fluid_let, fluid_set};
404///
405/// fluid_let!(static ENABLED: bool);
406///
407/// fn some_function() {
408/// fluid_set!(ENABLED, true);
409///
410/// // function body
411/// }
412/// ```
413///
414/// This is effectively equivalent to writing
415///
416/// ```no_run
417/// # use fluid_let::{fluid_let, fluid_set};
418/// #
419/// # fluid_let!(static ENABLED: bool);
420/// #
421/// fn some_function() {
422/// ENABLED.set(true, || {
423/// // function body
424/// });
425/// }
426/// ```
427///
428/// See also [crate-level documentation](index.html) for usage examples.
429#[macro_export]
430macro_rules! fluid_set {
431 ($variable:expr, $value:expr) => {
432 let _value_ = $value;
433 // This is safe because the users do not get direct access to the guard
434 // and are not able to drop it prematurely, thus maintaining invariants.
435 let _guard_ = unsafe { $variable.set_guard(&_value_) };
436 };
437}
438
439/// A global dynamic variable.
440///
441/// Declared and initialized by the [`fluid_let!`](macro.fluid_let.html) macro.
442///
443/// See [crate-level documentation](index.html) for examples.
444pub struct DynamicVariable<T: 'static> {
445 cell: &'static LocalKey<DynamicCell<T>>,
446}
447
448/// A resettable reference.
449#[doc(hidden)]
450pub struct DynamicCell<T> {
451 cell: UnsafeCell<Option<*const T>>,
452}
453
454/// Guard setting a new value of `DynamicCell<T>`.
455#[doc(hidden)]
456pub struct DynamicCellGuard<'a, T> {
457 old_value: Option<*const T>,
458 cell: &'a DynamicCell<T>,
459}
460
461impl<T> DynamicVariable<T> {
462 /// Initialize a dynamic variable.
463 ///
464 /// Use [`fluid_let!`](macro.fluid_let.html) macro to do this.
465 #[doc(hidden)]
466 pub const fn new(cell: &'static LocalKey<DynamicCell<T>>) -> Self {
467 Self { cell }
468 }
469
470 /// Access current value of the dynamic variable.
471 pub fn get<R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
472 self.cell.with(|current| {
473 // This is safe because the lifetime of the reference returned by get()
474 // is limited to this block so it cannot outlive any value set by set()
475 // in the caller frames.
476 f(unsafe { current.get() })
477 })
478 }
479
480 /// Bind a new value to the dynamic variable.
481 pub fn set<R>(&self, value: impl Borrow<T>, f: impl FnOnce() -> R) -> R {
482 self.cell.with(|current| {
483 // This is safe because the guard returned by set() is guaranteed to be
484 // dropped after the thunk returns and before anything else executes.
485 let _guard_ = unsafe { current.set(value.borrow()) };
486 f()
487 })
488 }
489
490 /// Bind a new value to the dynamic variable.
491 ///
492 /// # Safety
493 ///
494 /// The value is bound for the lifetime of the returned guard. The guard must be
495 /// dropped before the end of lifetime of the new and old assignment values.
496 /// If the variable is assigned another value while this guard is alive, it must
497 /// not be dropped until that new assignment is undone.
498 #[doc(hidden)]
499 pub unsafe fn set_guard(&self, value: &T) -> DynamicCellGuard<T> {
500 // We use transmute to extend the lifetime or "current" to that of "value".
501 // This is really the case when assignments are properly scoped.
502 unsafe fn extend_lifetime<'a, 'b, T>(r: &'a T) -> &'b T {
503 mem::transmute(r)
504 }
505 self.cell
506 .with(|current| extend_lifetime(current).set(value))
507 }
508}
509
510impl<T: Clone> DynamicVariable<T> {
511 /// Clone current value of the dynamic variable.
512 pub fn cloned(&self) -> Option<T> {
513 self.get(|value| value.cloned())
514 }
515}
516
517impl<T: Copy> DynamicVariable<T> {
518 /// Copy current value of the dynamic variable.
519 pub fn copied(&self) -> Option<T> {
520 self.get(|value| value.copied())
521 }
522}
523
524impl<T> DynamicCell<T> {
525 /// Makes a new empty cell.
526 pub fn empty() -> Self {
527 DynamicCell {
528 cell: UnsafeCell::new(None),
529 }
530 }
531
532 /// Makes a new cell with value.
533 #[cfg(feature = "static-init")]
534 pub fn with_static(value: &'static T) -> Self {
535 DynamicCell {
536 cell: UnsafeCell::new(Some(value)),
537 }
538 }
539
540 /// Access the current value of the cell, if any.
541 ///
542 /// # Safety
543 ///
544 /// The returned reference is safe to use during the lifetime of a corresponding guard
545 /// returned by a `set()` call. Ensure that this reference does not outlive it.
546 unsafe fn get(&self) -> Option<&T> {
547 (&*self.cell.get()).map(|p| &*p)
548 }
549
550 /// Temporarily set a new value of the cell.
551 ///
552 /// The value will be active while the returned guard object is live. It will be reset
553 /// back to the original value (at the moment of the call) when the guard is dropped.
554 ///
555 /// # Safety
556 ///
557 /// You have to ensure that the guard for the previous value is dropped after this one.
558 /// That is, they must be dropped in strict LIFO order, like a call stack.
559 unsafe fn set(&self, value: &T) -> DynamicCellGuard<T> {
560 DynamicCellGuard {
561 old_value: mem::replace(&mut *self.cell.get(), Some(value)),
562 cell: self,
563 }
564 }
565}
566
567impl<'a, T> Drop for DynamicCellGuard<'a, T> {
568 fn drop(&mut self) {
569 // We can safely drop the new value of a cell and restore the old one provided that
570 // get() and set() methods of DynamicCell are used correctly. That is, there must be
571 // no users of the new value which is about to be destroyed.
572 unsafe {
573 *self.cell.cell.get() = self.old_value.take();
574 }
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 use std::fmt;
583 use std::thread;
584
585 #[test]
586 fn cell_set_get_guards() {
587 // This is how properly scoped usage of DynamicCell works.
588 unsafe {
589 let v = DynamicCell::empty();
590 assert_eq!(v.get(), None);
591 {
592 let _g = v.set(&5);
593 assert_eq!(v.get(), Some(&5));
594 {
595 let _g = v.set(&10);
596 assert_eq!(v.get(), Some(&10));
597 }
598 assert_eq!(v.get(), Some(&5));
599 }
600 }
601 }
602
603 #[test]
604 fn cell_unsafe_set_get_usage() {
605 // The following is safe because references to constants are 'static,
606 // but it is not safe in general case allowed by the API.
607 unsafe {
608 let v = DynamicCell::empty();
609 let g1 = v.set(&5);
610 let g2 = v.set(&10);
611 assert_eq!(v.get(), Some(&10));
612 // Specifically, you CANNOT do this:
613 drop(g1);
614 // g1 *must* outlive g2 or else you'll that values are restored in
615 // incorrect order. Here we observe the value before "5" was set.
616 assert_eq!(v.get(), None);
617 // When g2 gets dropped it restores the value set by g1, which
618 // may not be a valid reference at this point.
619 drop(g2);
620 assert_eq!(v.get(), Some(&5));
621 // And now there's no one to reset the variable to None state.
622 }
623 }
624
625 #[test]
626 #[cfg(feature = "static-init")]
627 fn static_initializer() {
628 fluid_let!(static NUMBER: i32 = 42);
629
630 assert_eq!(NUMBER.copied(), Some(42));
631
632 fluid_let! {
633 static NUMBER_1: i32 = 100;
634 static NUMBER_2: i32;
635 static NUMBER_3: i32 = 200;
636 }
637
638 assert_eq!(NUMBER_1.copied(), Some(100));
639 assert_eq!(NUMBER_2.copied(), None);
640 assert_eq!(NUMBER_3.copied(), Some(200));
641 }
642
643 #[test]
644 fn dynamic_scoping() {
645 fluid_let!(static YEAR: i32);
646
647 YEAR.get(|current| assert_eq!(current, None));
648
649 fluid_set!(YEAR, 2019);
650
651 YEAR.get(|current| assert_eq!(current, Some(&2019)));
652 {
653 fluid_set!(YEAR, 2525);
654
655 YEAR.get(|current| assert_eq!(current, Some(&2525)));
656 }
657 YEAR.get(|current| assert_eq!(current, Some(&2019)));
658 }
659
660 #[test]
661 fn references() {
662 fluid_let!(static YEAR: i32);
663
664 // Temporary value
665 fluid_set!(YEAR, 10);
666 assert_eq!(YEAR.copied(), Some(10));
667
668 // Local reference
669 let current_year = 20;
670 fluid_set!(YEAR, ¤t_year);
671 assert_eq!(YEAR.copied(), Some(20));
672
673 // Heap reference
674 let current_year = Box::new(30);
675 fluid_set!(YEAR, current_year);
676 assert_eq!(YEAR.copied(), Some(30));
677 }
678
679 #[test]
680 fn thread_locality() {
681 fluid_let!(static THREAD_ID: i8);
682
683 THREAD_ID.set(0, || {
684 THREAD_ID.get(|current| assert_eq!(current, Some(&0)));
685 let t = thread::spawn(move || {
686 THREAD_ID.get(|current| assert_eq!(current, None));
687 THREAD_ID.set(1, || {
688 THREAD_ID.get(|current| assert_eq!(current, Some(&1)));
689 });
690 });
691 drop(t.join());
692 })
693 }
694
695 #[test]
696 fn convenience_accessors() {
697 fluid_let!(static ENABLED: bool);
698
699 assert_eq!(ENABLED.cloned(), None);
700 assert_eq!(ENABLED.copied(), None);
701
702 ENABLED.set(true, || assert_eq!(ENABLED.cloned(), Some(true)));
703 ENABLED.set(true, || assert_eq!(ENABLED.copied(), Some(true)));
704 }
705
706 struct Hash {
707 value: [u8; 16],
708 }
709
710 fluid_let!(pub static DEBUG_FULL_HASH: bool);
711
712 impl fmt::Debug for Hash {
713 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
714 let full = DEBUG_FULL_HASH.copied().unwrap_or(false);
715
716 write!(f, "Hash(")?;
717 if full {
718 for byte in &self.value {
719 write!(f, "{:02X}", byte)?;
720 }
721 } else {
722 for byte in &self.value[..4] {
723 write!(f, "{:02X}", byte)?;
724 }
725 write!(f, "...")?;
726 }
727 write!(f, ")")
728 }
729 }
730
731 #[test]
732 fn readme_example_code() {
733 let hash = Hash { value: [0; 16] };
734 assert_eq!(format!("{:?}", hash), "Hash(00000000...)");
735 fluid_set!(DEBUG_FULL_HASH, true);
736 assert_eq!(
737 format!("{:?}", hash),
738 "Hash(00000000000000000000000000000000)"
739 );
740 }
741}