Crate state [] [src]

state -- safe and effortless state management

This crate allows you to safely and effortlessly manage global and/or thread-local state through set and get methods. State is set and retrieved in a type-directed fashion: state allows one instance of a given type to be stored in global storage as well as n instances of a given type to be stored in n thread-local-storage slots. This makes state ideal for singleton instances, global configuration, and once-initialized state.

Usage

Include state in your Cargo.toml [dependencies]:

[dependencies]
state = "0.1"

Thread-local state management is not enabled by default. You can enable it via the "tls" feature:

[dependencies]
state = { version = "0.1", features = ["tls"] }

Global State

Global state is set via the set function and retrieved via the get function. The type of the value being set must be thread-safe and transferable across thread boundaries. In other words, it must satisfy Sync + Send + 'static.

Example

Set and later retrieve a value of type T:

state::set(T::new());
state::get::<T>();

Thread-Local State

Thread-local state is set via the set_local function and retrieved via the get_local function. The type of the value being set must be transferable across thread boundaries but need not be thread-safe. In other words, it must satisfy Send + 'static but not necessarily Sync. Values retrieved from thread-local state are exactly that: local to the current thread. As such, you cannot use thread-local state to synchronize across multiple threads.

Thread-local state is initialized on an as-needed basis. The function used to initialize the thread-local state is passed in as an argument to set_local. When the state is retrieved from a thread for the first time, the function is executed to generate the initial value. The function is executed at most once per thread. The same function is used for initialization across all threads.

Note: Rust reuses thread IDs across multiple threads. This means that is possible to set thread-local state in thread A, have that thread die, start a new thread B, and access the state set in A in B.

Example

Set and later retrieve a value of type T:

state::set_local(|| T::new());
state::get_local::<T>();

Use Cases

state is an optimal solution in several scenarios.

Singleton

Suppose you have the following structure which is initialized in main after receiving input from the user:

struct Configuration {
    name: String,
    number: isize,
    verbose: bool
}

fn main() {
    let config = Configuration {
        /* fill in structure at run-time from user input */
    };
}

You'd like to access this structure later, at any point in the program. Prior to state, assuming you needed to setup the structure after program start, your options were:

  1. Use a static mut and unsafe to set an Option<Configuration> to Some. Retrieve by checking for Some.
  2. Use lazy_static with a RwLock to set an Option<Configuration> to Some. Retrieve by locking and checking for Some.

With state, you simply call state::set and state::get, as follows:


fn main() {
    let config = Configuration {
        /* fill in structure at run-time from user input */
    };

    // Make the config avaiable globally.
    state::set(config);

    /* at any point later in the program */
    let config = state::get::<Configuration>();
}

Mutable, thread-local data

It is entirely safe to have an unsynchronized global object, as long as that object is accessible to a single thread at a time: the standard library's thread_local! macro allows this behavior to be encapsulated. state provides another, arguably simpler solution.

Say you want to count the number of invocations to a function per thread. You store the invocations in a struct InvokeCount(Cell<usize>) and use invoke_count.0.set(invoke_count.0.get() + 1) to increment the count. The following implements this using state:

struct InvokeCount(Cell<usize>);

fn function_to_measure() {
    let count = state::get_local::<InvokeCount>();
    count.0.set(count.0.get() + 1);
}

fn main() {
    // setup the initializer for thread-local state
    state::set_local(|| InvokeCount(Cell::new(0)));

    // spin up many threads that call `function_to_measure`.
    let mut threads = vec![];
    for i in 0..10 {
        threads.push(thread::spawn(|| {
            function_to_measure();
            state::get_local::<InvokeCount>().0.get()
        }));
    }

    // retrieve the thread-local counts
    let counts: Vec<usize> = threads.into_iter()
        .map(|t| t.join().unwrap())
        .collect();
}

Performance

state is heavily tuned to perform near-optimally when there are many threads. On average, state performs slightly worse than lazy_static when only a single thread is used to access a global variable, and slightly better than lazy_static when many threads are used to access a global variable. Keep in mind that state allows global initialization at any point in the program, while lazy_static initialization must be declared a priori. In other words, state's abilities are a superset of those in lazy_static.

When To Use

You should avoid using state as much as possible. Instead, prefer to pass state manually through your program.

Functions

get

Retrieves the global state for type T.

set

Sets the global state for type T if it has not been set before.

try_get

Attempts to retrieve the global state for type T.