Come posso creare un singleton globale e mutabile?

Qual è il modo migliore per creare e utilizzare una struct con una sola istanza nel sistema? Sì, questo è necessario, è il sottosistema OpenGL e fare copie multiple di questo e passarlo dappertutto aggiungerebbe confusione, piuttosto che alleggerirlo.

Il singleton deve essere il più efficiente ansible. Non sembra ansible memorizzare un object arbitrario sull’area statica, poiché contiene un Vec con un distruttore. La seconda opzione è quella di memorizzare un puntatore (non sicuro) sull’area statica, che punta a un singleton assegnato all’heap. Qual è il modo più comodo e più sicuro per farlo, mantenendo la syntax corretta.

Risposta non risposta

Evitare lo stato globale in generale. Invece, costruisci l’object da qualche parte in anticipo (forse in main ), quindi passa riferimenti mutabili a quell’object nei luoghi che ne hanno bisogno. Questo di solito rende il tuo codice più facile da ragionare e non richiede tanto di piegarsi all’indietro.

Guardati allo specchio prima di decidere che vuoi le variabili mutabili globali. Ci sono rari casi in cui è utile, ecco perché vale la pena saperlo fare.

Vuoi ancora fare uno …?

Usando pigro-statico

La cassa pigro-statica può togliere un po ‘del lavoro ingrato di creare un singleton (sotto). Ecco un vettore mutabile globale:

 #[macro_use] extern crate lazy_static; use std::sync::Mutex; lazy_static! { static ref ARRAY: Mutex> = Mutex::new(vec![]); } fn do_a_call() { ARRAY.lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", ARRAY.lock().unwrap().len()); } 

Se rimuovi il Mutex allora hai un singleton globale senza alcuna mutevolezza.

Un caso speciale: atomics

Se devi solo tenere traccia di un valore intero, puoi utilizzare direttamente un atomico :

 use std::sync::atomic::{AtomicUsize, Ordering}; static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); fn do_a_call() { CALL_COUNT.fetch_add(1, Ordering::SeqCst); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", CALL_COUNT.load(Ordering::SeqCst)); } 

Implementazione manuale, senza dipendenza

Questo è molto agguantato dall’implementazione di stdin di Rust 1.0 . Dovresti anche considerare la moderna implementazione di io::Lazy . Ho commentato in linea con cosa fa ogni linea.

 use std::sync::{Arc, Mutex, Once, ONCE_INIT}; use std::time::Duration; use std::{mem, thread}; #[derive(Clone)] struct SingletonReader { // Since we will be used in many threads, we need to protect // concurrent access inner: Arc>, } fn singleton() -> SingletonReader { // Initialize it to a null value static mut SINGLETON: *const SingletonReader = 0 as *const SingletonReader; static ONCE: Once = ONCE_INIT; unsafe { ONCE.call_once(|| { // Make it let singleton = SingletonReader { inner: Arc::new(Mutex::new(0)), }; // Put it in the heap so it can outlive this call SINGLETON = mem::transmute(Box::new(singleton)); }); // Now we give out a copy of the data that is safe to use concurrently. (*SINGLETON).clone() } } fn main() { // Let's use the singleton in a few threads let threads: Vec<_> = (0..10) .map(|i| { thread::spawn(move || { thread::sleep(Duration::from_millis(i * 10)); let s = singleton(); let mut data = s.inner.lock().unwrap(); *data = i as u8; }) }) .collect(); // And let's check the singleton every so often for _ in 0u8..20 { thread::sleep(Duration::from_millis(5)); let s = singleton(); let data = s.inner.lock().unwrap(); println!("It is: {}", *data); } for thread in threads.into_iter() { thread.join().unwrap(); } } 

Questo stampa:

 It is: 0 It is: 1 It is: 1 It is: 2 It is: 2 It is: 3 It is: 3 It is: 4 It is: 4 It is: 5 It is: 5 It is: 6 It is: 6 It is: 7 It is: 7 It is: 8 It is: 8 It is: 9 It is: 9 It is: 9 

Questo codice viene compilato con Rust 1.23.0. Le reali implementazioni di Stdin usano alcune caratteristiche instabili per tentare di liberare la memoria allocata, che questo codice non ha.

In realtà, probabilmente vorrai che SingletonReader implementa Deref e DerefMut modo da non dover entrare nell’object e bloccarlo da solo.

Tutto questo lavoro è ciò che lazy-static fa per te.