Подтвердить что ты не робот

Как создать глобальный, изменяемый синглтон?

Каков наилучший способ создания и использования структуры с одним экземпляром в системе? Да, это необходимо, это подсистема OpenGL, и, делая несколько копий этого и передавая ее повсюду, добавляет путаницу, а не освобождает ее.

Синглтон должен быть как можно более эффективным. Невозможно сохранить произвольный объект в статической области, так как он содержит Vec с деструктором. Второй вариант - сохранить (небезопасный) указатель на статической области, указывая на выделенную кучу одноэлемент. Самый удобный и безопасный способ сделать это, сохраняя синтаксис краткой.

4b9b3361

Ответ 1

Ответ без ответа

Избегайте глобального состояния в целом. Вместо этого создайте объект где-нибудь раньше (возможно, в main), а затем передайте изменяемые ссылки на этот объект в нужные ему места. Это, как правило, делает ваш код более легким для рассуждения и не требует столь большого наклона назад.

Посмотрите на себя в зеркало, прежде чем решите, что хотите глобальные изменяемые переменные. Есть редкие случаи, когда это полезно, поэтому почему стоит знать, как это сделать.

Еще хотите сделать один...?

Использование lazy-static

Ящик 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());
}

Ручная реализация

Это сильно отличается от реализации Rust 1.0 stdin. Я прокомментировал строку с тем, что делает каждая строка.

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 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 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.9, но обратите внимание, что выделенная память просочилась...

Действительно, вы, вероятно, захотите сделать SingletonReader реализовать Deref и DerefMut, поэтому вам не пришлось втыкать в объект и блокировать его самостоятельно.