Crate staticslot [] [src]

This crate provides a simple type, StaticSlot<T>, which is designed to make it easy to use static variables without much boilerplate or overhead. Usually you do not need any type of global variable, as it can introduce a number of problems into your code with bugs and testability. That being said, in certain applications a global variable is the most practical or efficient solution. This crate is targeted toward these uses.

Usage

A static slot is just a nullable pointer to some heap-allocated value with some extra features. We can declare one like this:

use staticslot::StaticSlot;

static MY_SLOT: StaticSlot<i32> = StaticSlot::NULL;

Here we're defining a static variable of type StaticSlot<i32> and initializing it to StaticSlot::NULL. In this state, our slot will start out "empty". To put an i32 value into the slot we can use the set() method:

use staticslot::StaticSlot;

static MY_SLOT: StaticSlot<i32> = StaticSlot::NULL;

unsafe {
    MY_SLOT.set(42);
}

There are two things we can observe from this. First, we can set the value without having MY_SLOT be static mut. This is because the slot provides atomic, interior mutability for us. Secondly, calling set() is unsafe; this is because the compiler cannot guarantee we will free the memory for our i32 when we are done with it.

If the value has been set, we can access it later using get():

use staticslot::StaticSlot;

static MY_SLOT: StaticSlot<i32> = StaticSlot::NULL;

unsafe {
    MY_SLOT.set(42);
}
println!("{}", *MY_SLOT.get().unwrap() + 100);

Since the slot may be empty, get() returns an Option. To clean up the memory when you are done, you can make the slot empty again by calling the drop() method. If you want to avoid unsafe code, you can put a dynamic lifetime on the value in the slot using the with() method, which introduces a scope for the value:

use staticslot::StaticSlot;

static MY_SLOT: StaticSlot<i32> = StaticSlot::NULL;

assert!(MY_SLOT.get() == None);
MY_SLOT.with(42, || {
    // MY_SLOT contains 42 inside this block.
    assert!(MY_SLOT.get() == Some(&mut 42));
});
assert!(MY_SLOT.get() == None);

If there is already a value in the slot, the previous value is restored at the end of the scope. Using with() guarantees that the memory for the value is cleaned up, and also allows you to nest calls with different values in the slot.

Unsized types

Since StaticSlot depends on atomic operations, only Sized types can be stored in it, as unsized types would require double-word atomics, which are not available on most architectures. It is possible to have an atomic unsized pointer by having a double pointer, but that would harm the performance for the general case.

If you need an unsized static slot (to hold a trait object, for example), you can simply put a Box<T> in the slot to get the desired semantics. Below is an example of putting Any into a static slot.

use staticslot::StaticSlot;
use std::any::Any;
use std::sync::Mutex;

static ANY: StaticSlot<Mutex<Box<Any + Send>>> = StaticSlot::NULL;

let value = Mutex::new(Box::new(String::from("hello")) as Box<Any + Send>);

ANY.with(value, || {
    if let Some(mutex) = ANY.get() {
        if let Some(string) = mutex.lock().unwrap().downcast_ref::<String>() {
            println!("It's a string({}): '{}'", string.len(), string);
        }
    }
});

This is useful when you need a singleton instance of some trait, but the implementation can vary.

Structs

StaticSlot

A container for a statically owned value.