Quel est le meilleur moyen de créer et d'utiliser une structure avec une seule instanciation dans le système? Oui, cela est nécessaire, il s’agit du sous-système OpenGL, et en faire plusieurs copies et le faire circuler partout ajouterait à la confusion, au lieu de le soulager.
Le singleton doit être aussi efficace que possible. Il ne semble pas possible de stocker un objet arbitraire dans la zone statique, car il contient un Vec
avec un destructeur. La deuxième option consiste à stocker un pointeur (non sécurisé) sur la zone statique, en pointant sur un singleton attribué au segment de mémoire. Quel est le moyen le plus pratique et le plus sûr de le faire, tout en conservant une syntaxe concise.
Réponse non-réponse
Évitez l'état global en général. Construisez plutôt l’objet quelque part tôt (peut-être dans main
), puis transmettez les références modifiables à cet objet aux endroits qui en ont besoin. Cela rendra généralement votre code plus facile à raisonner et n'exigera pas autant de déformation en arrière.
Regardez-vous bien dans le miroir avant de décider que vous voulez des variables mutables globales. Il y a de rares cas où cela est utile, c'est pourquoi il est important de savoir comment s'y prendre.
Toujours envie d'en faire un ...?
La caisse lazy-static peut vous éviter un peu de corvée de créer un singleton (ci-dessous). Voici un vecteur global modifiable:
#[macro_use]
extern crate lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref ARRAY: Mutex<Vec<u8>> = 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 vous supprimez le Mutex
, vous obtenez un singleton global sans aucune mutabilité.
Si vous avez seulement besoin de suivre une valeur entière, vous pouvez directement utiliser un atomique :
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));
}
Ceci est grandement dû à l'implémentation de stdin
de = (= Rust 1.0). Vous devriez également regarder l'implémentation moderne de io::Lazy
. J'ai commenté en ligne avec ce que chaque ligne fait.
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<Mutex<u8>>,
}
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();
}
}
Cela 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
Ce code est compilé avec Rust 1.23.0. Les vraies implémentations de Stdin
utilisent certaines fonctionnalités instables pour tenter de libérer la mémoire allouée, ce que ce code ne permet pas.
Vraiment, vous voudrez probablement faire SingletonReader
mettre en œuvre Deref
et DerefMut
afin que vous n'ayez pas à le faire piquez dans l'objet et verrouillez-le vous-même.
Tout ce travail est ce que lazy-static fait pour vous.
Veuillez noter que vous pouvez toujours utiliser la confidentialité normale Rust et la confidentialité au niveau du module pour contrôler l’accès à static
ou lazy_static
variable. Cela signifie que vous pouvez le déclarer dans un module ou même à l'intérieur d'une fonction et qu'il ne sera pas accessible en dehors de ce module/fonction. C'est bon pour contrôler l'accès:
use lazy_static::lazy_static; // 1.2.0
fn only_here() {
lazy_static! {
static ref NAME: String = String::from("hello, world!");
}
println!("{}", &*NAME);
}
fn not_here() {
println!("{}", &*NAME);
}
error[E0425]: cannot find value `NAME` in this scope
--> src/lib.rs:12:22
|
12 | println!("{}", &*NAME);
| ^^^^ not found in this scope
Cependant, la variable est toujours globale dans la mesure où il en existe une instance dans l'ensemble du programme.