rustoption-typelazy-initializationlazy-static

Lazy initialisation of lazy_static?


I've already had some success with lazy_static:

static ref WORD_COUNT_REPORTING_STEP_MUTEX: Arc<Mutex<usize>> = Arc::new(Mutex::new(0));
static ref INDEX_NAME: RwLock<String> = RwLock::new("".to_string());
static ref LANGUAGE_DETECTOR: LanguageDetector = LanguageDetectorBuilder::from_all_languages().with_preloaded_language_models().build();

But I'm really scratching my head over what should seemingly be a fairly easy pattern: set something of type Option<T> to None and then initialise on discovering that that Option is indeed None. If it is discovered that the Option is Some, skip the initialisation. After the initialisation block, get the Option and unwrap().

NB the type here is reqwest::blocking::client::Client.

So far I've got:

lazy_static! {
    static ref REQWEST_CLIENT_OPTION: Option<Client> = None;
}

Then, in a function:

...
match &*REQWEST_CLIENT_OPTION {
    Some(reqwest_client) => {
        info!("reqwest_client_option currently non-None");
    },
    None => {
        info!("reqwest_client_option currently None");
        
        // configuration and build of the client
        let es_path = r#"D:\apps\ElasticSearch\elasticsearch-8.6.2\config"#;
        set_var("ES_PATH_CONF", &es_path); // from crate tmp_env
        let mut buf = Vec::new();
        let cert_path = format!(r#"{es_path}\certs\http_ca.crt"#);
        let mut cert_file = File::open(cert_path).expect("problem opening certificate file");
        cert_file.read_to_end(&mut buf).ok();
        let cert = reqwest::Certificate::from_pem(&buf).expect("problem generating certificate"); // delivers cert OK
        let reqwest_client = Client::builder()
            .add_root_certificate(cert)
            .timeout(std::time::Duration::from_millis(5_000))
            .build().expect("problem building the request client");

        // now I try to set the Option...
        let mut static_client = &*REQWEST_CLIENT_OPTION;
        info!("static_client type {}", utilities::str_type_of(&static_client));
        // static_client type &core::option::Option<reqwest::blocking::client::Client>
        static_client = &Some(reqwest_client)
        // not now possible to check static_client. 
    }
}
let reqwest_client = REQWEST_CLIENT_OPTION.as_ref().unwrap();    
...

The above is largely guesswork. Needless to say, I've tried innumerable permutations involving *, & and things like let_mut. I hoped this might work:

*static_client = Some(reqwest_client);

... but this fails: " ^^^^^^^^^^^^^^ static_client is a & reference, so the data it refers to cannot be written".

At least the above code compiles OK.
The line "reqwest_client_option currently None" is printed.
But to my disappointment the last line causes a panic:

pyo3_runtime.PanicException: called `Option::unwrap()` on a `None` value

NB I also tried a variant with if let Some(reqwest_client) ... but ran into the same sorts of problems. This construction would probably be more elegant.


Solution

  • You can use OnceCell or, since you are making it static, OnceLock.

    For example:

    use std::sync::OnceLock;
    use reqwest::blocking::Client;
    
    static REQWEST_CLIENT: OnceLock<Client> = OnceLock::new();
    
    fn main(){
        let client = REQWEST_CLIENT.get_or_init(|| {
            Client::builder()
                // other options
                .build()
                .expect("problem building the reqwest client")
        });
    
        // use client
    }