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


140

システムでインスタンス化を1つだけ持つ構造体を作成して使用する最良の方法は何ですか?はい、これは必要です。これはOpenGLサブシステムであり、これの複数のコピーを作成し、どこにでも渡すと混乱を解消するのではなく、混乱を招きます。

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


1
OpenGLの既存のRustバインディングがこの同じ問題をどのように処理するかを見ましたか?
シェプマスター、2015年

20
はい、これは必要です。これはOpenGLサブシステムであり、これの複数のコピーを作成し、どこにでも渡すと混乱を解消するのではなく、混乱を招きます。=>これはrequiredの定義ではありません。(最初は)多分便利ですが、必要ではありません。
Matthieu M.

3
はい、あなたはポイントを持っています。とにかくOpenGLは大きなステートマシンであるため、そのクローンがどこにも存在せず、OpenGLエラーが発生するだけであることはほぼ確実です。
stevenkucera 2015年

回答:


198

非回答回答

一般的にグローバルな状態は避けてください。代わりに、オブジェクトを早期に(おそらくでmain)構築し、そのオブジェクトへの可変参照を、それを必要とする場所に渡します。これは通常、コードを推論するのを容易にし、後方への曲げをそれほど必要としません。

グローバルな可変変数が必要かどうかを決定する前に、ミラーで自分をよく見てください。これが役立つケースはまれですので、その方法を知っておく価値があります。

まだ作りたい…?

遅延静的の使用

怠惰な静的のクレートは、手動でシングルトンを作成するの重労働の一部を奪うことができます。以下は、グローバルな可変ベクトルです。

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

を削除すると、Mutex可変性のないグローバルシングルトンになります。

RwLock代わりにを使用しMutexて、複数の同時リーダーを許可することもできます。

once_cellの使用

once_cellクレートは、手動でシングルトンを作成するの重労働の一部を奪うことができます。以下は、グローバルな可変ベクトルです。

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

を削除すると、Mutex可変性のないグローバルシングルトンになります。

RwLock代わりにを使用し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から大幅に変更され最新のRustにいくつかの調整が加えられています。また、の最新の実装も確認する必要がありますio::Lazy。各行の機能に合わせてインラインでコメントしました。

use std::sync::{Arc, Mutex, Once};
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::new();

    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.42.0でコンパイルされます。の実際の実装でStdinは、不安定な機能を使用して、割り当てられたメモリを解放しようとしますが、このコードではそうしません。

本当に、あなたはおそらくしたいと思いSingletonReader実装するDerefDerefMut、あなたはオブジェクトに突くと、それを自分でロックする必要はありませんでしたので。

この作業はすべて、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つあるという点で、変数は依然としてグローバルです。


72
多くの考えの後、私はシングルトンを使用しないことを確信し、代わりにグローバル変数をまったく使用せずにすべてを渡します。レンダラーにアクセスする関数が明確であるため、コードをより自己文書化します。シングルトンに戻したい場合は、逆にするよりも簡単です。
stevenkucera 2015年

4
答えてくれてありがとう、それはとても役に立ちました。lazy_static!の有効な使用例として私が見ているものを説明するコメントをここに付けようと思ったところです。私はそれを使用して、モジュール(共有オブジェクト)のロード/アンロードを可能にするCアプリケーションへのインターフェースを作成しています。錆コードはこれらのモジュールの1つです。ロード時にグローバルを使用するよりも多くのオプションがありません。なぜならmain()をまったく制御できず、コアアプリケーションがモジュールとどのようにインターフェースするかがわからないからです。私は基本的に、MODが読み込まれた後、ランタイムに追加できるもののベクトルを必要としていました。
Moises Silva

1
@MoisesSilva シングルトンを必要とする理由は常にありますが、それが使用される多くのケースでそれを使用する必要はありません。コードを知らなくても、Cアプリケーションが各モジュールに「ユーザーデータ」を返すことを許可し、void *それが各モジュールのメソッドに戻される可能性があります。これは、Cコードの典型的な拡張パターンです。アプリケーションがこれを許可しておらず、変更できない場合は、はい、シングルトンが良い解決策になる可能性があります。
Shepmaster

3
@Worikはなぜ説明してくれますか?私は、ほとんどの言語で悪い考えである何かをすることを人々にやめさせます(OPでさえ、グローバルは彼らのアプリケーションにとって悪い選択であることに同意しました)。これが一般的な意味です。次に、それを行う方法について2つの解決策を示します。lazy_staticRust 1.24.1 の例をテストしたところ、問題なく動作しました。external staticここにはどこもありません。おそらく、答えを完全に理解したことを確認するために、自分の側で物事を確認する必要があるかもしれません。
Shepmaster

1
@Worikクレートの基本的な使用方法についてヘルプが必要な場合は、The Rust Programming Languageを再読することをお勧めします。推測ゲーム作成に関する章の依存関係を追加する方法を示します。
シェプマスター2018年

0

グローバルアクセスにはSpinLockを使用します。

#[derive(Default)]
struct ThreadRegistry {
    pub enabled_for_new_threads: bool,
    threads: Option<HashMap<u32, *const Tls>>,
}

impl ThreadRegistry {
    fn threads(&mut self) -> &mut HashMap<u32, *const Tls> {
        self.threads.get_or_insert_with(HashMap::new)
    }
}

static THREAD_REGISTRY: SpinLock<ThreadRegistry> = SpinLock::new(Default::default());

fn func_1() {
    let thread_registry = THREAD_REGISTRY.lock();  // Immutable access
    if thread_registry.enabled_for_new_threads {
    }
}

fn func_2() {
    let mut thread_registry = THREAD_REGISTRY.lock();  // Mutable access
    thread_registry.threads().insert(
        // ...
    );
}

変更可能な状態(シングルトンではない)が必要な場合は、詳細についてRustですべきでないことを参照してください

お役に立てば幸いです。


-1

私自身の重複した質問に答える

Cargo.toml:

[dependencies]
lazy_static = "1.4.0"

クレートルート(lib.rs):

#[macro_use]
extern crate lazy_static;

初期化(安全でないブロックは不要):

/// EMPTY_ATTACK_TABLE defines an empty attack table, useful for initializing attack tables
pub const EMPTY_ATTACK_TABLE: AttackTable = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

lazy_static! {
    /// KNIGHT_ATTACK is the attack table of knight
    pub static ref KNIGHT_ATTACK: AttackTable = {
        let mut at = EMPTY_ATTACK_TABLE;
        for sq in 0..BOARD_AREA{
            at[sq] = jump_attack(sq, &KNIGHT_DELTAS, 0);
        }
        at
    };
    ...

編集:

マクロを必要としないonce_cellでそれを解決することに成功しました。

Cargo.toml:

[dependencies]
once_cell = "1.3.1"

square.rs:

use once_cell::sync::Lazy;

...

/// AttackTable type records an attack bitboard for every square of a chess board
pub type AttackTable = [Bitboard; BOARD_AREA];

/// EMPTY_ATTACK_TABLE defines an empty attack table, useful for initializing attack tables
pub const EMPTY_ATTACK_TABLE: AttackTable = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

/// KNIGHT_ATTACK is the attack table of knight
pub static KNIGHT_ATTACK: Lazy<AttackTable> = Lazy::new(|| {
    let mut at = EMPTY_ATTACK_TABLE;
    for sq in 0..BOARD_AREA {
        at[sq] = jump_attack(sq, &KNIGHT_DELTAS, 0);
    }
    at
});

2
この回答は、すでに議論されlazy_staticている、より新しい既存の回答と比較して新しいものを提供しませんonce_cell。SOで重複としてマークするポイントは、冗長な情報を持たないようにすることです。
Shepmaster
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.