rust - 如何在 Rust 中制作安全的静态单例?

标签 rust

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

我发现对于一堆不安全的事情,重要的是要确保你不会泄漏内存;如果您开始使用 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 风格的保护锁来确保多个测试不会同时运行,然后不安全地访问某种静态可变值。

我想也许this implementation would work , 但实际上它失败了,因为有时竞争条件会导致重复执行 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 中的测试运行器不提供任何类型的单一入口点。

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

最佳答案

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

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

然后在你的测试中调用

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

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

关于rust - 如何在 Rust 中制作安全的静态单例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27221504/

相关文章:

rust - 在终身问题上需要帮助

compiler-errors - 在 Rust 中,我可以从模块中公开函数数组吗?

rust - 派生 PartialEq 时无法移出包含盒装特征对象的枚举的借用内容

rust - 在 Rust 中,如何强制一个被阻止读取文件的线程恢复?

Rust:在默认方法实现中返回特征对象

rust - 按位还是匹配?

function-pointers - 如何在 Rust 中将匿名函数作为参数传递?

macros - 如何将 C 预处理器宏与 Rust 的 FFI 一起使用?

rust - 如何使用 [stdout] 和 [sterr] 作为 Command stdout 的前缀?

scala - 如何将 Scala 匿名特征实现转换为 Rust?