[−][src]Crate fluid_let
Dynamically-scoped variables.
Dynamic or fluid variables are a handy way to define global configuration values. They come from the Lisp family of languages where they are relatively popular in this role.
Defining dynamic variables
Dynamic variables are global therefore they should be defined as static
.
fluid_let!
macro is used to define dynamic variables:
use std::fs::File; use fluid_let::fluid_let; fluid_let!(static LOG_FILE: File);
The actual type of LOG_FILE
variable will be Option<&File>
: that is, possibly unspecified
reference to a file. All dynamic variables have None
as their default value, unless
a particular value is set for them.
Setting dynamic variables
Dynamic variables are given values with set
:
let log_file = File::create("/tmp/log.txt")?; LOG_FILE.set(&log_file, || { // // logs will be redirected to /tmp/log.txt in this block // });
The new value is in effect within the dynamic extent of the assignment, that is within
the closure passed to set
. Once the closure returns, the previous value of the variable
is restored. You can nest assignments arbitrarily:
LOG_FILE.set(open_log("/tmp/log.txt"), || { // // log to /tmp/log.txt here // LOG_FILE.set(open_log("/dev/null"), || { // // log to /dev/null for a bit // }); // // log to /tmp/log.txt again // });
Accessing dynamic variables
The current value of dynamic variable can be retrieved with get
:
fn write_log(msg: &str) -> io::Result<()> { LOG_FILE.get(|current| { if let Some(mut log_file) = current { log_file.write_all(msg.as_bytes())?; log_file.write_all(b"\n")?; } Ok(()) }) }
Note the following:
- dynamic variable may be not set, so you have to handle both
Options
- dynamic variable itself is global (it has
'static
lifetime) but its values are local and have shorter lifetimes, therefore they are accessible only within the closure get
forwards the value returned by the closure (this is true forset
as well)
Dynamic value assignment has dynamic scope, not lexical one (duh...)
Therefore in the following program function foo
will log to different files,
depending on whether it is called from bar
or zog
.
fn foo() { write_log("hello from foo()"); } fn bar() { LOG_FILE.set(open_log("/tmp/bar.log"), || { foo(); }); } fn zog() { LOG_FILE.set(open_log("/tmp/zog.log"), || { foo(); }); }
This behavior is the whole point of dynamic variables.
Thread safety
Dynamic variables are global and thread-local. That is, each thread gets its own independent instance of a dynamic variable. Values set in one thread are visible only in this thread. Other threads will not see any changes in values of their dynamic variables and may have completely different configurations.
Note, however, that this does not free you from the usual synchronization concerns when shared
objects are involved. Dynamic variables hold references to objects. Therefore is is entirely
possible to bind the same object to a dynamic variable and access it from multiple threads.
In this case you will probably need some synchronization to use the shared object in a safe
manner, just like you would do when using Arc
or something.
Macros
fluid_let | Defines global dynamic variables. |
Structs
DynamicVariable | A global dynamic variable. |