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<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 } }
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.
https://play.rust-lang.org/?gist=bc213c667b3d757a5409580d69baf51e&version=stable&backtrace=0 https://play.rust-lang.org/?gist=bc213c667b3d757a5409580d69baf51e&version=stable&backtrace=0
/// 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.