如何在Rust中制作安全的静态单体?

如何在Rust中制作安全的静态单体?,rust,Rust,这是一个有争议的话题,所以让我从解释我的用例开始,然后谈谈实际问题 我发现,对于一些不安全的事情,确保不泄漏内存是很重要的;如果您开始使用transmute()和forget(),这实际上非常容易做到。例如,将一个已装箱的实例传递给C代码任意时间,然后将其取回,并使用transmute将其“复活” 假设我有一个用于这种API的安全包装器: trait Foo {} struct CBox; impl CBox { /// Stores value in a bound C api, f

这是一个有争议的话题,所以让我从解释我的用例开始,然后谈谈实际问题

我发现,对于一些不安全的事情,确保不泄漏内存是很重要的;如果您开始使用
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
    }
}
如何创建这种类型的静态单例对象

它必须使用
信号量
样式的保护锁来确保多个测试不会同时运行,然后安全地访问某种静态可变值

我想可能吧,但实际上它失败了,因为偶尔竞争条件会导致重复执行
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));
    }
}
请特别注意,与可以确定入口点(main)始终在单个任务中运行的普通程序不同,Rust中的测试运行程序不提供任何类似的单个入口点


显然,除了指定任务的最大数量之外,还有其他的任务;考虑到几十个测试,只有少数需要做这类事情,并且仅针对这一种情况将测试任务池限制为一个是缓慢而毫无意义的。

它看起来像是一个用于:

然后在你的测试中打电话

INIT.doit(|| unsafe { init(); });
one
保证您的
init
将只执行一次,无论您调用
init.doit()

多少次,这使事情更符合人体工程学。它对每个变量执行一次与静态
基本相同的操作,但将其包装为实现
Deref
的类型,以便您可以像正常引用一样访问它

用法如下所示():


如果你想要变异,这就是问题的重点。我刚刚遇到了一个类似的“单例”需求,最终得到了一个使用lazy_static的好解决方案(至少在我的情况下——顺便说一句,我只是在学习Rust),基本上如下所述:没有
INIT.doit(…)
。我尝试了
INIT.call\u一次(..)
,但是,发现另一个错误
INIT
被“中毒”。
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
INIT.doit(|| unsafe { init(); });
#[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);
}
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
}