4

我有一个惰性静态结构,我希望能够在程序执行开始时将其设置为某个随机值,然后再获取。这个愚蠢的小片段可以用作示例:

use lazy_static::lazy_static;
use std::sync::RwLock;

struct Answer(i8);

lazy_static! {
    static ref ANSWER: RwLock<Option<Answer>> = RwLock::new(None);
}

fn answer_question() {
    *ANSWER.write().unwrap() = Some(Answer(42));
}

fn what_is_the_answer() -> &'static Answer {
    ANSWER
        .read()
        .unwrap()
        .as_ref()
        .unwrap()
}

此代码无法编译:

error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:15:5
   |
15 |        ANSWER
   |   _____^
   |  |_____|
   | ||
16 | ||         .read()
17 | ||         .unwrap()
   | ||_________________- temporary value created here
18 | |          .as_ref()
19 | |          .unwrap()
   | |__________________^ returns a value referencing data owned by the current function

我知道您不能返回对临时值的引用。但我想返回一个ANSWER静态的引用——与临时相反!我想这是RwLockReadGuard第一次调用unwrap返回的问题吗?

我可以通过更改返回类型来编译代码:

fn what_is_the_answer() -> RwLockReadGuard<'static, Option<Answer>> {
    ANSWER
        .read()
        .unwrap()
}

但是现在调用代码变得非常不符合人体工程学 - 我必须进行两次额外调用才能获得实际值:

what_is_the_answer().as_ref().unwrap()

我可以以某种方式ANSWER从该函数返回对静态的引用吗?我RwLockReadGuard<&Answer>可以通过某种方式映射它来返回一个可能吗?

4

2 回答 2

2

once_cell专为此而设计:使用.set(...).unwrap()inanswer_question.get().unwrap()in what_is_the_answer

于 2020-08-10T18:49:55.213 回答
1

据我了解您的意图,在Answer初始化时无法计算的值,lazy_static但取决于仅在answer_question调用时才知道的参数。以下可能不是最优雅的解决方案,但它允许对&'static依赖于仅在运行时已知的参数的值进行 -reference。

基本方法是使用两个lazy_static值,其中一个用作“代理”来进行必要的同步,另一个是值本身。这避免了每次访问时都必须访问多层锁和展开Option-values ANSWER

-valueANSWER通过等待 a 来初始化CondVar,这将在计算值时发出信号。然后该值被放置在lazy_static并且从那时起不可移动。因此&'static是可能的(见get_the_answer())。我选择String了示例类型。请注意,ANSWER不调用的访问generate_the_answer()将导致初始化永远等待,从而使程序死锁。

use std::{sync, thread};

lazy_static::lazy_static! {
    // A proxy to synchronize when the value is generated
    static ref ANSWER_PROXY: (sync::Mutex<Option<String>>, sync::Condvar) = {
        (sync::Mutex::new(None), sync::Condvar::new())
    };
    // The actual value, which is initialized from the proxy and stays in place
    // forever, hence allowing &'static access
    static ref ANSWER: String = {
        let (lock, cvar) = &*ANSWER_PROXY;
        let mut answer = lock.lock().unwrap();
        loop {
            // As long as the proxy is None, the answer has not been generated
            match answer.take() {
                None => answer = cvar.wait(answer).unwrap(),
                Some(answer) => return answer,
            }
        }
    };
}

// Generate the answer and place it in the proxy. The `param` is just here
// to demonstrate we can move owned values into the proxy
fn generate_the_answer(param: String) {
    // We don't need a thread here, yet we can
    thread::spawn(move || {
        println!("Generating the answer...");
        let mut s = String::from("Hello, ");
        s.push_str(&param);
        thread::sleep(std::time::Duration::from_secs(1));

        let (lock, cvar) = &*ANSWER_PROXY;
        *lock.lock().unwrap() = Some(s);
        cvar.notify_one();
        println!("Answer generated.");
    });
}

// Nothing to see here, except that we have a &'static reference to the answer
fn get_the_answer() -> &'static str {
    println!("Asking for the answer...");
    &ANSWER
}

fn main() {
    println!("Hello, world!");

    // Accessing `ANSWER` without generating it will deadlock!
    //get_the_answer();

    generate_the_answer(String::from("John!"));
    println!("The answer is \"{}\"", get_the_answer());

    // The second time a value is generated, noone is listening.
    // This is the flipside of `ANSWER` being a &'static
    generate_the_answer(String::from("Peter!"));
    println!("The answer is still \"{}\"", get_the_answer());
}
于 2020-08-10T18:31:23.660 回答