システム内のインスタンス化が1つだけの構造体を作成して使用する最良の方法は何ですか?はい、これは必要です。これはOpenGLサブシステムであり、これを複数コピーしてどこにでも渡すと、混乱を緩和するのではなく混乱を招くことになります。
シングルトンは可能な限り効率的である必要があります。 Vec
にデストラクタが含まれているため、静的領域に任意のオブジェクトを格納することは不可能と思われます。 2番目のオプションは、静的領域に(安全でない)ポインターを格納し、ヒープに割り当てられたシングルトンを指すことです。構文を簡潔に保ちながらこれを行う最も便利で安全な方法は何ですか。
無回答
一般的にグローバル状態を避けてください。代わりに、オブジェクトを早い段階で(おそらくmain
で)構築し、そのオブジェクトへの可変参照を必要な場所に渡します。これにより、通常、コードの推論が容易になり、逆向きに曲げる必要がなくなります。
グローバルな可変変数が必要だと判断する前に、ミラーで自分をよく見てください。それが有用なまれなケースがあるので、それはそれを行う方法を知る価値がある理由です。
まだ作りたい...?
lazy-static クレートは、シングルトンを作成するための面倒な作業の一部を取り除くことができます(以下)。以下は、グローバルな可変ベクトルです。
#[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());
}
Mutex
を削除すると、可変性のないグローバルシングルトンになります。
整数値のみを追跡する必要がある場合は、直接 atomic を使用できます。
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));
}
これは、 Rust 1.0 stdin
の1.0実装 から大きく制限されています。 io::Lazy
の最新の実装もご覧ください。各行が何をするのかインラインでコメントしました。
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();
}
}
これは印刷します:
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
このコードはRust 1.23.0でコンパイルされます。 Stdin
の実際の実装は、いくつかの不安定な機能を使用して、割り当てられたメモリを解放しようとしますが、このコードはそうしません。
本当に、あなたはおそらくSingletonReader
に Deref
と DerefMut
を実装させたいので、オブジェクトに突っ込んで自分でロックする必要はありません。
この作業はすべて、lazy-staticが行うことです。
static
またはlazy_static
変数へのアクセスを制御するために、通常のRustスコープとモジュールレベルのプライバシーを引き続き使用できることに注意してください。これは、モジュール内または関数内で宣言することができ、そのモジュール/関数の外部からアクセスできないことを意味します。これは、アクセスの制御に適しています。
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
ただし、プログラム全体に存在する変数のインスタンスが1つあるという点で、変数はまだグローバルです。