web-dev-qa-db-ja.com

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

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

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

92
stevenkucera

無回答

一般的にグローバル状態を避けてください。代わりに、オブジェクトを早い段階で(おそらく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の実際の実装は、いくつかの不安定な機能を使用して、割り当てられたメモリを解放しようとしますが、このコードはそうしません。

本当に、あなたはおそらくSingletonReaderDerefDerefMut を実装させたいので、オブジェクトに突っ込んで自分でロックする必要はありません。

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

110
Shepmaster