Search code examples
mongodbasynchronousrustlazy-static

alternative to using 'await' with lazy_static! macro in rust?


I want to use Async MongoDB in a project.

I don't want to pass around the client because it would need to go around multiple tasks and threads. So I kept a static client using lazy_static. However, I can't use await in the initialization block.

What can I do to work around this?

Suggestions for doing it without lazy_static are also welcome.

use std::env;
use futures::stream::StreamExt;
use mongodb::{
    bson::{doc, Bson},
    options::ClientOptions,
    Client,
};

lazy_static! {
    static ref MONGO: Option<Client> = {
        if let Ok(token) = env::var("MONGO_AUTH") {
            if let Ok(client_options) = ClientOptions::parse(&token).await
                                                                     ^^^^^
            {
                if let Ok(client) = Client::with_options(client_options) {
                    return Some(client);
                }
            }
        }
        return None;
    };
}

Solution

  • I went with this approach based on someone's suggestion in rust forums.

    static MONGO: OnceCell<Client> = OnceCell::new();
    static MONGO_INITIALIZED: OnceCell<tokio::sync::Mutex<bool>> = OnceCell::new();
    
    pub async fn get_mongo() -> Option<&'static Client> {
        // this is racy, but that's OK: it's just a fast case
        let client_option = MONGO.get();
        if let Some(_) = client_option {
            return client_option;
        }
        // it hasn't been initialized yet, so let's grab the lock & try to
        // initialize it
        let initializing_mutex = MONGO_INITIALIZED.get_or_init(|| tokio::sync::Mutex::new(false));
    
        // this will wait if another task is currently initializing the client
        let mut initialized = initializing_mutex.lock().await;
        // if initialized is true, then someone else initialized it while we waited,
        // and we can just skip this part.
        if !*initialized {
            // no one else has initialized it yet, so
    
            if let Ok(token) = env::var("MONGO_AUTH") {
                if let Ok(client_options) = ClientOptions::parse(&token).await {
                    if let Ok(client) = Client::with_options(client_options) {
                        if let Ok(_) = MONGO.set(client) {
                            *initialized = true;
                        }
                    }
                }
            }
        }
        drop(initialized);
        MONGO.get()
    }