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