这是一个有争议的主题,所以让我先解释一下用例,然后再讨论实际问题。

我发现对于一堆不安全的东西,确保你不会泄漏记忆;如果您开始使用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));
    }
}


特别要注意的是,与普通程序不同,在常规程序中您可以确定入口点(主)始终在单个任务中运行,Rust中的测试运行程序不提供任何种类的单个入口

显然没有指定最大任务数;在进行数十种测试的情况下,只需要执行少量操作即可,并且仅针对这种情况将测试任务池限制为一个,这既缓慢又毫无意义。

#1 楼

它看起来像是std::sync::Once的用例:

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


然后在您的测试调用中

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


Once保证无论您调用init多少次,INIT.doit()都只会执行一次。

#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
}


评论


如果要进行突变,那就是问题的关键所在。如何创建全局的可变单例?

– Shepmaster
16年8月14日在21:56

快速评论,我刚刚遇到了类似的“单个”需求,最终得到了一个使用lazy_static的好的解决方案(至少对我来说是这样-顺便说一句,我只是在学习Rust),基本上是在这里描述的:users.rust-lang .org / t / ownership-issue-with-a-static-hashmap / ...

– carueda
5月14日23:46