简体   繁体   中英

Return a reference to a T inside a lazy static RwLock<Option<T>>?

I have a lazy static struct that I want to be able to set to some random value in the beginning of the execution of the program, and then get later. This little silly snippet can be used as an example:

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

This code fails to compile:

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

I know you can not return a reference to a temporary value. But I want to return a reference to ANSWER which is static - the very opposite of temporary! I guess it is the RwLockReadGuard that the first call to unwrap returns that is the problem?

I can get the code to compile by changing the return type:

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

But now the calling code becomes very unergonomic - I have to do two extra calls to get to the actual value:

what_is_the_answer().as_ref().unwrap()

Can I somehow return a reference to the static ANSWER from this function? Can I get it to return a RwLockReadGuard<&Answer> maybe by mapping somehow?

once_cell is designed for this: use .set(...).unwrap() in answer_question and .get().unwrap() in what_is_the_answer .

As far as I understand your intention, the value of Answer can't be computed while it is being initialized in the lazy_static but depends on parameters known only when answer_question is called. The following may not be the most elegant solution, yet it allows for having a &'static -reference to a value that depends on parameters only known at runtime.

The basic approach is to use two lazy_static -values, one of which serves as a "proxy" to do the necessary synchronization, the other being the value itself. This avoids having to access multiple layers of locks and unwrapping of Option -values whenever you access ANSWER .

The ANSWER -value is initialized by waiting on a CondVar , which will signal when the value has been computed. The value is then placed in the lazy_static and from then on unmovable. Hence &'static is possible (see get_the_answer() ). I have chosen String as the example-type. Notice that accessing ANSWER without calling generate_the_answer() will cause the initialization to wait forever, deadlocking the program.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM