43

This is something of a controversial topic, so let me start by explaining my use case, and then talk about the actual problem.

I find that for a bunch of unsafe things, it's important to make sure that you don't leak memory; this is actually quite easy to do if you start using transmute() and forget(). For example, passing a boxed instance to C code for an arbitrary amount of time, then fetching it back out and 'resurrecting it' by using transmute.

Imagine I have a safe wrapper for this sort of API:

trait Foo {}
struct CBox;

impl CBox {
    /// Stores value in a bound C api, forget(value)
    fn set<T: Foo>(value: T) {
        // ...
    }

    /// Periodically call this and maybe get a callback invoked
    fn poll(_: Box<Fn<(EventType, Foo), ()> + Send>) {
        // ...
    }
}

impl Drop for CBox {
    fn drop(&mut self) {
        // Safely load all saved Foo's here and discard them, preventing memory leaks
    }
}

To test this is actually not leaking any memory, I want some tests like this:

#[cfg(test)]
mod test {

    struct IsFoo;
    impl Foo for IsFoo {}
    impl Drop for IsFoo {
        fn drop(&mut self) {
            Static::touch();
        }
    }

    #[test]
    fn test_drops_actually_work() {
        guard = Static::lock(); // Prevent any other use of Static concurrently
        Static::reset(); // Set to zero
        {
            let c = CBox;
            c.set(IsFoo);
            c.set(IsFoo);
            c.poll(/*...*/);
        }
        assert!(Static::get() == 2); // Assert that all expected drops were invoked
        guard.release();
    }
}

How can you create this type of static singleton object?

It must use a Semaphore style guard lock to ensure that multiple tests do not concurrently run, and then unsafely access some kind of static mutable value.

I thought perhaps this implementation would work, but practically speaking it fails because occasionally race conditions result in a duplicate execution of init:

/// Global instance
static mut INSTANCE_LOCK: bool = false;
static mut INSTANCE: *mut StaticUtils = 0 as *mut StaticUtils;
static mut WRITE_LOCK: *mut Semaphore = 0 as *mut Semaphore;
static mut LOCK: *mut Semaphore = 0 as *mut Semaphore;

/// Generate instances if they don't exist
unsafe fn init() {
    if !INSTANCE_LOCK {
        INSTANCE_LOCK = true;
        INSTANCE = transmute(box StaticUtils::new());
        WRITE_LOCK = transmute(box Semaphore::new(1));
        LOCK = transmute(box Semaphore::new(1));
    }
}

Note specifically that unlike a normal program where you can be certain that your entry point (main) is always running in a single task, the test runner in Rust does not offer any kind of single entry point like this.

Other, obviously, than specifying the maximum number of tasks; given dozens of tests, only a handful need to do this sort of thing, and it's slow and pointless to limit the test task pool to one just for this one case.

5 Answers 5

41
+50

It looks like a use case for std::sync::Once:

use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;

Then in your tests call

INIT.doit(|| unsafe { init(); });

Once guarantees that your init will only be executed once, no matter how many times you call INIT.doit().

Sign up to request clarification or add additional context in comments.

1 Comment

There is no INIT.doit(..). I tried INIT.call_once(..) instead, but however, found another error that INIT was "poisoned".
28

See also lazy_static, which makes things a little more ergonomic. It does essentially the same thing as a static Once for each variable, but wraps it in a type that implements Deref so that you can access it like a normal reference.

Usage looks like this (from the documentation):

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref HASHMAP: HashMap<u32, &'static str> = {
        let mut m = HashMap::new();
        m.insert(0, "foo");
        m.insert(1, "bar");
        m.insert(2, "baz");
        m
    };
    static ref COUNT: usize = HASHMAP.len();
    static ref NUMBER: u32 = times_two(21);
}

fn times_two(n: u32) -> u32 { n * 2 }

fn main() {
    println!("The map has {} entries.", *COUNT);
    println!("The entry for `0` is \"{}\".", HASHMAP.get(&0).unwrap());
    println!("A expensive calculation on a static results in: {}.", *NUMBER);
}

Note that autoderef means that you don't even have to use * whenever you call a method on your static variable. The variable will be initialized the first time it's Deref'd.

However, lazy_static variables are immutable (since they're behind a reference). If you want a mutable static, you'll need to use a Mutex:

lazy_static! {
    static ref VALUE: Mutex<u64>;
}

impl Drop for IsFoo {
    fn drop(&mut self) {
        let mut value = VALUE.lock().unwrap();
        *value += 1;
    }
}

#[test]
fn test_drops_actually_work() {
    // Have to drop the mutex guard to unlock, so we put it in its own scope
    {
        *VALUE.lock().unwrap() = 0;
    }
    {
        let c = CBox;
        c.set(IsFoo);
        c.set(IsFoo);
        c.poll(/*...*/);
    }
    assert!(*VALUE.lock().unwrap() == 2); // Assert that all expected drops were invoked
}

2 Comments

If you want mutation, that's the point of the question How do I create a global, mutable singleton?.
A quick comment that I just came across a similar "singleton" need, ending up with a good solution using lazy_static (at least for my case -- btw, I'm just learning Rust) basically as described here: users.rust-lang.org/t/ownership-issue-with-a-static-hashmap/…
8

While LazyLock is still nightly-only, OnceLock has just been stabilized (since Rust 1.70). Here is an example of how it could be used:

use std::sync::OnceLock;

static SOME_URL: OnceLock<String> = OnceLock::new();

fn read_url_from_somewhere() -> String {
    // This function will only be called once.
    // Imagine we get this URL from a file or another service.
    format!("https://a-funny-url-here.com")
}

fn get_url() -> &'static str {
    SOME_URL.get_or_init(|| {
        read_url_from_somewhere()
    })    
}

fn main() {
    println!("The URL from the main thread: {}", get_url());
    std::thread::spawn(|| {
        println!("Same URL from another thread: {}", get_url());
    }).join().unwrap();
}

Output:

The URL from the main thread: https://a-funny-url-here.com
Same URL from another thread: https://a-funny-url-here.com

Comments

4

If you're willing to use nightly Rust you can use SyncLazy instead of the external lazy_static crate:

#![feature(once_cell)]

use std::collections::HashMap;

use std::lazy::SyncLazy;

static HASHMAP: SyncLazy<HashMap<i32, String>> = SyncLazy::new(|| {
    println!("initializing");
    let mut m = HashMap::new();
    m.insert(13, "Spica".to_string());
    m.insert(74, "Hoyten".to_string());
    m
});

fn main() {
    println!("ready");
    std::thread::spawn(|| {
        println!("{:?}", HASHMAP.get(&13));
    }).join().unwrap();
    println!("{:?}", HASHMAP.get(&74));

    // Prints:
    //   ready
    //   initializing
    //   Some("Spica")
    //   Some("Hoyten")
}

Comments

1

Using thread_local!() from the standard library with mutability of the singleton and without unsafe code or Mutex wrapping.
For types of singletons that do not need to be dropped, you don't need to track any additional state, because they are dropped at the end of the thread in which they are declared (usually the main thread).

use std::cell::RefCell;

/// Global instance
thread_local! {
    static INSTANCE_LOCK: RefCell<bool> = RefCell::new(false);
    static INSTANCE: RefCell<StaticUtils> = RevCell::new(StaticUtils::new());
    static WRITE_LOCK: RefCell<Semaphore> = RevCell::new(Semaphore::new(1));
    static LOCK: RefCell<Semaphore> = RevCell::new(Semaphore::new(1));
}

/// Accessing the singletons
INSTANCE_LOCK.with(|lock| { lock.borrow() ‹read the lock› });
INSTANCE.with(|utils| { utils.borrow() ‹read the utils› });
WRITE_LOCK.with(|sem| { sem.borrow() ‹read the semaphore› });
LOCK.with(|lock| { lock.borrow() ‹read the lock› });

/// Accessing the singletons mutually
INSTANCE_LOCK.with(|lock| { lock.borrow_mut() ‹change the lock› });
INSTANCE.with(|utils| { utils.borrow_mut() ‹change the utils› });
WRITE_LOCK.with(|sem| { sem.borrow_mut() ‹change the semaphore› });
LOCK.with(|lock| { lock.borrow_mut() ‹change the lock› });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.