这是一个有争议的话题,所以让我先解释一下我的用例,然后再谈谈实际问题。
我发现,对于一堆不安全的东西,确保你不会泄漏记忆是很重要的;如果您开始使用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中的测试运行器不提供任何类型的单一入口点。
显然,除了指定最大任务数外;给出了几十个测试,只有极少数需要做这种事情,并且将测试任务池限制为只有一个这样的情况是缓慢而毫无意义的。
答案 0 :(得分:28)
它看起来像std::sync::Once
的用例:
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
然后在你的测试中调用
INIT.doit(|| unsafe { init(); });
Once
保证您的init
只会被执行一次,无论您拨打INIT.doit()
多少次。
答案 1 :(得分:13)
另见lazy_static,它使事情更符合人体工程学。它与每个变量的静态Once
基本相同,但将其包装在实现Deref
的类型中,以便您可以像普通引用一样访问它。
用法如下所示(from the documentation):
#[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
'时初始化。
但是,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
}