[][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 for set 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.