Search code examples
rustrust-tokio

rust async update of HashMap with "or_insert_with"


I am trying to lazily populate a HashMap from a DB with async calls when the map does not already have an entry.

Rust compiler warns async closures are unstable but that I should try async {.

I am trying to follow that suggestion but I get the expected a FnOnce<()> closure error in the comments:

use std::collections::HashMap;
use tokio::runtime::Runtime;

async fn get_from_store(key: String) -> String {
    // pretend to get this from an async sqlx db call
    String::from(format!("value-for-{key}"))
}

async fn do_work() {
    let mut map: HashMap<String, String> = HashMap::new();

    let key = String::from("key1");

    // the compiler advised async closures were unstable and...
    // to use an async block, remove the `||`: `async {` (rustc E0658)
    map.entry(key.clone())
        .or_insert_with(async { get_from_store(key) }.await);

    // the above now gives the error:
    //expected a `FnOnce<()>` closure, found `impl Future<Output = String>...xpected an `FnOnce<()>` closure, found `impl Future<Output = String>`

    for (key, value) in &map {
        println!("{}: {}", key, value);
    }
}

fn main() {
    let runtime = Runtime::new().unwrap_or_else(|e| panic!("Haha: {e}"));
    let result = do_work();
    match runtime.block_on(result) {
        _ => {}
    }
}

There may be reasons not to update HashMap via async but the error above gave me hope I was just doing this wrong...


Solution

  • You won't be able to use .await in that position. That's because it requires that the return type of the closure is a Future, but the signature of or_insert_with does not expect that.

    I would do it more simply, keeping the .await inside the async function that you already have:

    use std::collections::hash_map::Entry;
    
    async fn do_work() {
        let mut map: HashMap<String, String> = HashMap::new();
        let key = String::from("key1");
    
        if let Entry::Vacant(entry) = map.entry(key.clone()) {
            entry.insert(get_from_store(key).await);
        }
    
        for (key, value) in &map {
            println!("{}: {}", key, value);
        }
    }