Home > OS >  Rust's Lazy init (OnceCell crate) does not work everywhere
Rust's Lazy init (OnceCell crate) does not work everywhere

Time:08-21

I'm writing a discord bot in Rust with Serenity. In order to populate some embed headers, I need bot-related info (user & owner, hereafter named BOT) and I'm trying to use the Lazy struct from the once_cell crate to make that happen.

The catch is that this BOT data can only be queried using async functions with serenity's Http module and I'm struggling to make that work.

I set up a few slash-commands that each have their own handler function. When I dereference BOT (this is what's supposed to evaluate it (only once), right ?) from one of those handler functions, my Lazy closure blocks on the first .await it encounters.

On the other hand, it just works when I dereference BOT from main (tokio async).

The following code therefore prints "0,1,2,3" when dereferencing BOT from main but only "0,1" when doing so from a specific slash-command handler (BoxedFuture) :

use once_cell::sync::Lazy;
use serenity::{http::Http, model::user::User};

pub struct Bot {
    pub user: User,
    pub owner: User,
}

pub static BOT: Lazy<Bot> = Lazy::new(|| {
    println!("0");
    let http_client = Http::new(env!("DISCORD_BOT_TOKEN"));

    futures::executor::block_on(async {
        println!("1");
        let user = http_client
            .get_current_user()
            .await
            .expect("An HTTP client error occured")
            .into();

        println!("2");
        let owner = http_client
            .get_current_application_info()
            .await
            .expect("An HTTP client error occured")
            .owner;

        println!("3");
        Bot { user, owner }
    })
});

Can anyone explain why that is ?

CodePudding user response:

You should never block inside an async runtime. Never, ever.

You can use tokio::sync::OnceCell instead of once_cell:

pub async fn get_bot() -> &'static Bot {
    static BOT: tokio::sync::OnceCell<Bot> = tokio::sync::OnceCell::const_new();
    BOT.get_or_init(|| async {
        println!("0");
        let http_client = Http::new(env!("DISCORD_BOT_TOKEN"));

        println!("1");
        let user = http_client
            .get_current_user()
            .await
            .expect("An HTTP client error occured")
            .into();

        println!("2");
        let owner = http_client
            .get_current_application_info()
            .await
            .expect("An HTTP client error occured")
            .owner;

        println!("3");
        Bot { user, owner }
    })
    .await
}
  • Related