¿Cómo creo un singleton global y mutable?

¿Cuál es la mejor manera de crear y usar una estructura con solo una instanciación en el sistema? Sí, esto es necesario, es el subsistema OpenGL, y hacer copias múltiples de esto y pasarlo a todas partes agregaría confusión, en lugar de aliviarlo.

El singleton debe ser lo más eficiente posible. No parece posible almacenar un objeto arbitrario en el área estática, ya que contiene un Vec con un destructor. La segunda opción es almacenar un puntero (inseguro) en el área estática, apuntando a un singleton asignado en el montón. ¿Cuál es la forma más conveniente y segura de hacer esto, manteniendo la syntax concisa?

Respuesta sin respuesta

Evita el estado global en general. En su lugar, construya el objeto en algún lugar temprano (quizás en main ), luego pase referencias mutables a ese objeto en los lugares que lo necesiten. Esto generalmente hará que su código sea más fácil de razonar y no requiera tanta inclinación hacia atrás.

Mírate a ti mismo en el espejo antes de decidir que quieres variables mutables globales. Hay casos excepcionales en los que es útil, por eso vale la pena saber cómo hacerlo.

¿Todavía quieres hacer uno …?

Usando lazy-static

La caja lazy-static puede quitar parte de la monotonía de crear un singleton (abajo). Aquí hay un vector mutable global:

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

Si elimina el Mutex entonces tiene un singleton global sin ninguna mutabilidad.

Un caso especial: atomics

Si solo necesita rastrear un valor entero, puede usar directamente un atómico :

 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)); } 

Implementación manual, libre de dependencia

Esto está muy claro desde la implementación de Rust 1.0 stdin . También debe ver la implementación moderna de io::Lazy . He comentado en línea con lo que hace cada línea.

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

Esto se imprime:

 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 

Este código se comstack con Rust 1.23.0. Las implementaciones reales de Stdin usan algunas características inestables para intentar liberar la memoria asignada, lo que no ocurre con este código.

Realmente, es probable que desee hacer que SingletonReader implemente Deref y DerefMut para que no tenga que meterse en el objeto y bloquearlo usted mismo.

Todo este trabajo es lo que hace lazy-static para ti.