How can you make a safe static singleton in Rust?

前端 未结 2 801
独厮守ぢ
独厮守ぢ 2020-11-22 07:15

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 thin

相关标签:
2条回答
  • 2020-11-22 07:46

    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
    }
    
    0 讨论(0)
  • 2020-11-22 07:57

    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().

    0 讨论(0)
提交回复
热议问题