Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
76 views
in Technique[技术] by (71.8m points)

How can you make a safe static singleton in Rust?

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.

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.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It looks like a use case for std::sync::Once:

use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;

Then in your tests call

INIT.doit(|| unsafe { init(); });

Once guarantees that your init will only be executed once, no matter how many times you call INIT.doit().


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...