グローバルで変更可能なシングルトンを作成するにはどうすればいいですか? 質問する

グローバルで変更可能なシングルトンを作成するにはどうすればいいですか? 質問する

システム内でインスタンスが 1 つだけの構造体を作成して使用する最善の方法は何ですか? はい、これは必要です。これは OpenGL サブシステムであり、これを複数コピーしてあちこちに渡すと、混乱が軽減されるのではなく、混乱が増すことになります。

シングルトンは可能な限り効率的である必要があります。静的領域にはデストラクタが含まれているため、任意のオブジェクトを静的領域に格納することはできないようですVec。2 番目のオプションは、ヒープに割り当てられたシングルトンを指す (安全でない) ポインタを静的領域に格納することです。構文を簡潔に保ちながら、これを行う最も便利で安全な方法は何ですか?

ベストアンサー1

非回答回答

一般的に、グローバル状態は避けてください。代わりに、オブジェクトを早い段階で (おそらく 内でmain) 構築し、そのオブジェクトへの可変参照を必要な場所に渡します。これにより、通常、コードを理解しやすくなり、それほど苦労する必要がなくなります。

グローバル可変変数が必要かどうかを決める前に、鏡で自分自身をよく見てください。まれに役に立つ場合もあるので、やり方を知っておく価値はあります。

まだ作りたいですか…?

チップ

以下のソリューションでは:

  • 削除するとMutexすると、変更不可能なグローバル シングルトンが作成されます。
  • また、RwLock複数の同時リーダーを許可するMutexには、 の代わりに を使用します。

使用std::sync::OnceLock

OnceLockRust 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 つのインスタンスが存在するという点で、依然としてグローバルです。

おすすめ記事