你如何能在Rust中构build一个安全的静态单体?
这是一个有争议的话题,所以我首先解释我的用例,然后谈论实际问题。
我发现,对于一些不安全的事情,确保你不会泄漏内存是很重要的。 如果你开始使用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 } }
要testing这实际上不泄漏任何内存,我想要一些像这样的testing:
#[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(); } }
你怎么能创build这种types的静态单例对象?
它必须使用Semaphore
风格的防护锁来确保多个testing不会同时运行,然后不安全地访问某种静态可变值。
我想也许这个实现可能会工作 ,但实际上它失败,因为偶尔的竞争条件导致重复执行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的testing运行程序不提供任何types的单一入口点。
其他,显然,比指定的最大数量的任务; 考虑到几十个testing,只有极less数需要做这样的事情,并且将testing任务池限制为仅用于这种情况是缓慢和毫无意义的。
它看起来像std::sync::Once
的用例。 你可以这样使用它:
use std::sync::{Once, ONCE_INIT}; static INIT: Once = ONCE_INIT;
然后在你的testing呼叫
INIT.doit(|| unsafe { init(); });
Once
结构保证你的init
只会被执行一次,不pipe你多less次调用INIT.doit()
。
你可能也想看看std::sync::StaticMutex
来防止不必要的并发访问某些数据。 文档中的例子说明了一切:
use std::sync::{StaticMutex, MUTEX_INIT}; static LOCK: StaticMutex = MUTEX_INIT; unsafe { let _guard = LOCK.lock().unwrap(); // critical section... } // automatically unlocked in `_guard`'s destructor
另请参阅lazy_static ,它使事情更符合人体工程学。 它对每个variables都做一个基本相同的事情,但是将其包装在一个Deref中,以便可以像正常的引用那样访问它。
用法如下所示(来自文档):
#[macro_use(lazy_static)] extern crate lazy_static; 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!("An expensive calculation on a static results in: {}.", *NUMBER); }
请注意,autoderef意味着你甚至不必在你的静态variables上调用一个方法时使用*
。 variables将在Deref
第一次被初始化。
但是,lazy_staticvariables是不可变的(因为它们在引用之后)。 如果你想要一个可变的静态,你将需要使用互斥。 (实际上, StaticMutex
在1.10中已经被弃用了, StaticMutex
在StaticMutex
中包装Mutex
lazy_static!
)。
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 }