2014-12-01 14 views
17

Jest to kwestia kontrowersyjna, więc zacznę od wyjaśnienia mojego przypadku użycia, a następnie porozmawiam o rzeczywistym problemie.Jak możesz stworzyć bezpieczny statyczny singleton w Rust?

Uważam, że dla wielu niebezpiecznych rzeczy, ważne jest, aby upewnić się, że nie wyciek pamięci; jest to całkiem łatwe do zrobienia, jeśli zaczniesz używać transmute() i forget(). Na przykład przekazywanie instancji pudełkowej do kodu C przez dowolny czas, a następnie jej pobieranie i "wskrzeszanie" za pomocą transmute.

Wyobraźmy mam bezpieczne opakowanie dla tego rodzaju 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 
    } 
} 

Aby przetestować ten jest faktycznie nie przecieka żadnej pamięci, chcę niektóre testy tak:

#[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(); 
    } 
} 

Jak możesz stworzyć ten typ statycznego obiektu singleton?

Należy użyć blokady straży Semaphore stylu, aby zapewnić, że wiele badań nie równocześnie uruchomić, a następnie w sposób niebezpieczny dostęp do pewnego rodzaju statycznej wartości zmienny.

Myślałem, że może this implementation would work, ale praktycznie rzecz biorąc to nie dlatego, że czasami warunki wyścigowe spowodować duplikatu realizacji 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 konkretnie, że w przeciwieństwie do zwykłego programu, gdzie można mieć pewność, że Twój punkt wejścia (main) zawsze działa w jednym zadaniu, biegacz testowy w Rust nie oferuje żadnego rodzaju pojedynczego punktu wejścia takiego jak ten.

Inne, oczywiście, niż określenie maksymalnej liczby zadań; biorąc pod uwagę dziesiątki testów, tylko garstka musi robić tego typu rzeczy, a powolne i bezcelowe jest ograniczanie puli zadań testowych do jednego tylko dla tego jednego przypadku.

Odpowiedz

26

Wygląda przypadku użycia do std::sync::Once:

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

Następnie w badaniach zadzwonić

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

Once gwarancji, że init zostanie wykonany tylko raz, bez względu na to ile razy można zadzwonić INIT.doit().

8

Zobacz także lazy_static, co sprawia, że ​​rzeczy są nieco bardziej ergonomiczne. Zasadniczo to samo, co statyczna Once dla każdej zmiennej, ale opakowuje ją w typ, który implementuje Deref, dzięki czemu można uzyskać do niego dostęp jak normalne odniesienie.

Wykorzystanie wygląda następująco (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); 
} 

Zauważ, że autoderef oznacza, że ​​nie trzeba nawet używać * dowolnym momencie wywołania metody na zmiennej statycznej. Zmienna zostanie zainicjowana po raz pierwszy Deref 'd.

Jednak zmienne lazy_static są niezmienne (ponieważ znajdują się za odnośnikiem).Jeśli chcesz zmienne statyczne, trzeba użyć 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 
} 
+2

Jeśli chcesz mutację, że o to chodzi w rachubę [Jak mogę utworzyć globalną, zmienny singleton?] (Http: // stackoverflow.com/q/27791532/155423). – Shepmaster

Powiązane problemy