システム内でインスタンスが 1 つだけの構造体を作成して使用する最善の方法は何ですか? はい、これは必要です。これは OpenGL サブシステムであり、これを複数コピーしてあちこちに渡すと、混乱が軽減されるのではなく、混乱が増すことになります。
シングルトンは可能な限り効率的である必要があります。静的領域にはデストラクタが含まれているため、任意のオブジェクトを静的領域に格納することはできないようですVec
。2 番目のオプションは、ヒープに割り当てられたシングルトンを指す (安全でない) ポインタを静的領域に格納することです。構文を簡潔に保ちながら、これを行う最も便利で安全な方法は何ですか?
ベストアンサー1
非回答回答
一般的に、グローバル状態は避けてください。代わりに、オブジェクトを早い段階で (おそらく 内でmain
) 構築し、そのオブジェクトへの可変参照を必要な場所に渡します。これにより、通常、コードを理解しやすくなり、それほど苦労する必要がなくなります。
グローバル可変変数が必要かどうかを決める前に、鏡で自分自身をよく見てください。まれに役に立つ場合もあるので、やり方を知っておく価値はあります。
まだ作りたいですか…?
チップ
以下のソリューションでは:
使用std::sync::OnceLock
OnceLock
Rust 1.70.0 で安定化されました。これを使用して依存性のない実装を取得できます。
use std::sync::{Mutex, OnceLock};
fn array() -> &'static Mutex<Vec<u8>> {
static ARRAY: OnceLock<Mutex<Vec<u8>>> = OnceLock::new();
ARRAY.get_or_init(|| 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());
}
LazyLock
ヘルパー関数を削除できる はまだ不安定であることに注意してくださいarray()
。
使用lazy-static
の怠惰な静的crate を使用すると、手動でシングルトンを作成する手間が省けます。以下はグローバルな可変ベクターです。
use lazy_static::lazy_static; // 1.4.0
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());
}
使用once_cell
の一度セルcrate を使用すると、手動でシングルトンを作成する手間が省けます。以下はグローバルな可変ベクターです。
use once_cell::sync::Lazy; // 1.3.1
use std::sync::Mutex;
static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| 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());
}
使用std::sync::LazyLock
標準ライブラリはプロセスonce_cell
の機能を追加すること、現在はLazyLock
:
#![feature(lazy_cell)]
use std::sync::{LazyLock, Mutex};
static ARRAY: LazyLock<Mutex<Vec<u8>>> = LazyLock::new(|| 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());
}
特別なケース:原子
整数値のみを追跡する必要がある場合は、直接原子:
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
これは、割り当てや不要な間接参照を回避するために を使用するなど、現代のRustに適応された同じアイデアです。MaybeUninit
の現代的な実装も見てみましょう。io::Lazy
各行が何をするかをインラインでコメントしました。
use std::sync::{Mutex, Once};
use std::time::Duration;
use std::{mem::MaybeUninit, thread};
struct SingletonReader {
// Since we will be used in many threads, we need to protect
// concurrent access
inner: Mutex<u8>,
}
fn singleton() -> &'static SingletonReader {
// Create an uninitialized static
static mut SINGLETON: MaybeUninit<SingletonReader> = MaybeUninit::uninit();
static ONCE: Once = Once::new();
unsafe {
ONCE.call_once(|| {
// Make it
let singleton = SingletonReader {
inner: Mutex::new(0),
};
// Store it to the static var, i.e. initialize it
SINGLETON.write(singleton);
});
// Now we give out a shared reference to the data, which is safe to use
// concurrently.
SINGLETON.assume_init_ref()
}
}
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.55.0 でコンパイルされます。
この作業はすべて、lazy-static または once_cell が実行します。
「グローバル」の意味
通常の Rust スコープとモジュール レベルのプライバシーを使用して、static
またはlazy_static
変数へのアクセスを制御できることに注意してください。つまり、モジュール内または関数内で宣言しても、そのモジュール / 関数の外部からはアクセスできません。これはアクセスを制御するのに適しています。
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 つのインスタンスが存在するという点で、依然としてグローバルです。