你如何使锈安全静态单?你如何使锈安全静态单?(How can you make a safe sta

2019-05-10 10:01发布

这是一个有争议的话题的东西,所以让我解释我的使用情况下启动,然后谈谈实际问题。

我发现了一堆不安全的事情,一定要确保你没有内存泄漏是非常重要的; 其实这是很容易做到,如果你开始使用transmute()forget() 例如,传递一个装箱实例为C代码的时间的任意的量,然后取出它背出,并通过使用“复活它transmute

想象一下,我对这种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
    }
}

为了测试这个实际上没有任何泄漏的内存,我想这样的一些测试:

#[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();
    }
}

你怎么能创建这种类型的静态单一对象的?

它必须使用Semaphore风格防护锁,以确保多个测试不同时运行,然后不安全访问某种静态的可变值。

我想也许这个实现会工作 ,但实际而言,因为偶尔的竞争条件导致的重复执行失败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));
    }
}

特别要注意不像一个正常的程序,你可以肯定,你的入口点(主)在一个任务中总是处于运行状态,在拉斯特测试运行不提供任何类型的单一入口点是这样的。

其他,显然,不是指定的最大任务数; 给几十个测试,只有极少数需要做这样的事情,这是缓慢的,毫无意义的测试任务池限制在一个只为这一个情况。

Answer 1:

它看起来像一个用例std::sync::Once

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

然后在你的测试中调用

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

Once保证你的init将只执行一次,不管你有多少次调用INIT.doit()



Answer 2:

又见lazy_static ,这让事情变得更符合人体工程学。 它本质上是做同样的事情,作为一个静态Once每个变量,但是把它包装到一个实现了类型Deref ,这样就可以像一个正常参考访问它。

使用看起来像这样( 从文件 ):

#[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);
}

需要注意的是autoderef意味着,你甚至不必使用*当你调用一个方法,你的静态变量。 变量将首次初始化它的Deref “d。

然而,lazy_static变量是不可变的(因为他们是一个参考后面)。 如果你想有一个可变的静电,你需要使用一个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
}


文章来源: How can you make a safe static singleton in Rust?
标签: rust