I want to store some aysnc operations in a hashmap. Then get a async operation list based on the user input string list. By search the site,I found this question
How can I put an async function into a map in Rust?
The difference is that I want to use the stored operation hashmap across diffrent tokio thread. So I rewrite with once_cell + RwLock.
static MAPS: Lazy<Arc<RwLock<HashMap<&'static str, CalcFn>>>> = Lazy::new(|| {
let map = Arc::new(RwLock::new(HashMap::new()));
let mut map_lock = map.write().unwrap();
map_lock.insert(
"add",
Box::new(
move |a: i32, b: i32| -> Pin<Box<dyn Future<Output = BoxedResult<i32>>>> {
Box::pin(add(a, b))
},
) as CalcFn,
);
map_lock.insert(
"sub",
Box::new(
move |a: i32, b: i32| -> Pin<Box<dyn Future<Output = BoxedResult<i32>>>> {
Box::pin(sub(a, b))
},
) as CalcFn,
);
map.clone()
});
The lazy initialization seems boring and takes a lot of lines in code beacause I may have 20+ async operations.
Could someone kindly offer some comments to write this example in a "cleaner" way?
The first thing to do is move as much as possible from the insert operation into another function.
fn insert_async_fn<K, F, Fut>(map: &mut HashMap<K, CalcFn>, name: K, f: F)
where
K: Eq + Hash,
F: Fn(i32, i32) -> Fut + Send + Sync + 'static,
Fut: Future<Output = BoxedResult<i32>> + Send + 'static,
{
map.insert(name, Box::new(move |a, b| Box::pin(f(a, b))));
}
// Used like this
insert_async_fn(&mut map, "add", add);
insert_async_fn(&mut map, "sub", sub);
You don't need to use clone
at the end. All you need to do is drop the lock before returning.
drop(map_lock);
map
You don't need the Arc
. Arc
can be used to ensure a value stays alive across multiple threads, but static
already ensures that.
In the code you posted, you also don't need RwLock
, but it might be necessary if you are modifying the map after construction.
If you're using the latest stable version of Rust, you can use LazyLock
from the standard library. It works the same as the one from once_cell
.
You usually don't need errors to be Sync
, but you do often need futures to be Send
, so that they can be spawned on multithreaded runtimes.
That leaves us with this:
static MAPS: LazyLock<RwLock<HashMap<&'static str, CalcFn>>> = LazyLock::new(|| {
let mut map = HashMap::new();
insert_async_fn(&mut map, "add", add);
insert_async_fn(&mut map, "sub", sub);
RwLock::new(map)
});
fn insert_async_fn<K, F, Fut>(map: &mut HashMap<K, CalcFn>, name: K, f: F)
where
K: Eq + Hash,
F: Fn(i32, i32) -> Fut + Send + Sync + 'static,
Fut: Future<Output = BoxedResult<i32>> + Send + 'static,
{
map.insert(name, Box::new(move |a, b| Box::pin(f(a, b))));
}