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, &current_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}