Crate fluid_let[][src]

Expand description

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.

Declaring dynamic variables

fluid_let! macro is used to declare dynamic variables. Dynamic variables are global, therefore they must be declared as static:

use std::fs::File;

use fluid_let::fluid_let;

fluid_let!(static LOG_FILE: File);

The actual type of LOG_FILE variable is Option<&File>: that is, possibly absent reference to a file. All dynamic variables have None as their default value, unless a particular value is set for them.

If you enable the "static-init" feature, it is also possible to provide 'static initialization for types that allow it:

fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);

Here LOG_LEVEL has Some(&LogLevel::Info) as its default value.

Setting dynamic variables

set is used to give value to a dynamic variable:

let log_file: File = open("/tmp/log.txt");

LOG_FILE.set(&log_file, || {
    //
    // logs will be redirected to /tmp/log.txt in this block
    //
});

Note that you store an immutable reference in the dynamic variable. You can’t directly modify the dynamic variable value after setting it, but you can use something like Cell or RefCell to circumvent that.

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.

If you do not need precise control over the extent of the assignment, you can use the fluid_set! macro to assign until the end of the scope:

use fluid_let::fluid_set;

fn chatterbox_function() {
    fluid_set!(LOG_FILE, open("/dev/null"));
    //
    // logs will be written to /dev/null in this function
    //
}

Obviously, you can also nest assignments arbitrarily:

LOG_FILE.set(open("A.txt"), || {
    // log to A.txt here
    LOG_FILE.set(open("/dev/null"), || {
        // log to /dev/null for a bit
        fluid_set!(LOG_FILE, open("B.txt"));
        // log to B.txt starting with this line
        {
            fluid_set!(LOG_FILE, open("C.txt"));
            // but in this block log to C.txt
        }
        // before going back to using B.txt here
    });
    // and logging to A.txt again
});

Accessing dynamic variables

get is used to retrieve the current value of a dynamic variable:

fn write_log(msg: &str) -> io::Result<()> {
    LOG_FILE.get(|current| {
        if let Some(mut log_file) = current {
            write!(log_file, "{}\n", msg)?;
        }
        Ok(())
    })
}

Current value of the dynamic variable is passed to the provided closure, and the value returned by the closure becomes the value of the get() call.

This somewhat weird access interface is dictated by safety requirements. The dynamic variable itself is global and thus has 'static lifetime. However, its values usually have shorter lifetimes, as short as the corresponing set() call. Therefore, access reference must have even shorter lifetime.

If the variable type implements Clone or Copy then you can use cloned and copied convenience accessors to get a copy of the current value:

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum LogLevel {
    Debug,
    Info,
    Error,
}

fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);

fn write_log(level: LogLevel, msg: &str) -> io::Result<()> {
    if level < LOG_LEVEL.copied().unwrap() {
        return Ok(());
    }
    LOG_FILE.get(|current| {
        if let Some(mut log_file) = current {
            write!(log_file, "{}\n", msg)?;
        }
        Ok(())
    })
}

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 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 it is entirely possible to bind the same object with internal mutability 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 and friends.

Features

Currently, there is only one optional feature: "static-init", gating static initialization of dynamic variables:

fluid_let!(static LOG_LEVEL: LogLevel = LogLevel::Info);
//                                    ~~~~~~~~~~~~~~~~

The API for accessing known-initialized variables has not stabilized yet and may be subject to changes.

Macros

Declares global dynamic variables.

Binds a value to a dynamic variable.

Structs

A global dynamic variable.