这是一个有争议的话题,所以让我先解释一下我的使用情况,然后再谈论实际问题。
我发现对于一些不安全的事情来说,确保不泄漏内存非常重要;如果你开始使用transmute()和forget(),这实际上是相当容易做到的。例如,将一个盒装实例传递给C代码进行任意时间的操作,然后通过使用transmute将其取回并“复活”它。
想象一下,我有一个针对这种API的安全包装器:
为了测试这段代码是否真的没有内存泄漏问题,我需要进行类似以下的测试:
请注意,与普通程序不同的是,在 Rust 中,您不能确定您的入口点(main)始终在单个任务中运行,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
样式的守卫锁来确保多个测试不会同时运行,然后不安全地访问某种静态可变值。
我认为也许这个实现可以工作,但实际上它失败了,因为偶尔会出现竞争条件导致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 中,您不能确定您的入口点(main)始终在单个任务中运行,Rust 中的测试运行器没有像这样的任何单个入口点。
显然,除了指定任务的最大数量之外,只有少数测试需要执行此类操作;对于这种情况,将测试任务池限制为一个是缓慢且无意义的。
INIT.doit(..)
。我尝试使用INIT.call_once(..)
代替,但是发现另一个错误,即INIT
被“污染”了。 - BingLi224