This is something of a controversial topic, so let me start by explaining my use case, and then talk about the actual problem.
I find that for a bunch of unsafe things, it's important to make sure that you don't leak memory; this is actually quite easy to do if you start using transmute()
and forget()
. For example, passing a boxed instance to C code for an arbitrary amount of time, then fetching it back out and 'resurrecting it' by using transmute
.
Imagine I have a safe wrapper for this sort of API:
trait Foo {} struct CBox; impl CBox { /// Stores value in a bound C api, forget(value) fn set(value: T) { // ... } /// Periodically call this and maybe get a callback invoked fn poll(_: Box + Send>) { // ... } } impl Drop for CBox { fn drop(&mut self) { // Safely load all saved Foo's here and discard them, preventing memory leaks } }
To test this is actually not leaking any memory, I want some tests like this:
#[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(); } }
How can you create this type of static singleton object?
It must use a Semaphore
style guard lock to ensure that multiple tests do not concurrently run, and then unsafely access some kind of static mutable value.
I thought perhaps this implementation would work, but practically speaking it fails because occasionally race conditions result in a duplicate execution of 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)); } }
Note specifically that unlike a normal program where you can be certain that your entry point (main) is always running in a single task, the test runner in Rust does not offer any kind of single entry point like this.
Other, obviously, than specifying the maximum number of tasks; given dozens of tests, only a handful need to do this sort of thing, and it's slow and pointless to limit the test task pool to one just for this one case.