use std::{
collections::HashMap,
hash::Hash,
sync::{Mutex, OnceLock},
};
pub struct OnceMap<K, V> {
inner: OnceLock<Mutex<HashMap<K, V>>>,
}
impl<K, V> OnceMap<K, V> {
pub const fn new() -> Self {
Self {
inner: OnceLock::new(),
}
}
fn map(&self) -> &Mutex<HashMap<K, V>> {
self.inner.get_or_init(|| Mutex::new(HashMap::new()))
}
}
impl<K: Eq + Hash + Clone, V: Clone> OnceMap<K, V> {
pub fn get_or_init_with<F>(&self, key: &K, init: F) -> V
where
F: FnOnce() -> V,
{
let mut guard = self.map().lock().expect("OnceMap mutex poisoned");
if let Some(v) = guard.get(key) {
return v.clone();
}
let v = init();
guard.insert(key.clone(), v.clone());
v
}
pub async fn get_or_init_async<F, Fut>(&self, key: &K, init: F) -> V
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = V>,
{
if let Some(v) = self.get(key) {
return v;
}
let v = init().await;
self.map()
.lock()
.expect("OnceMap mutex poisoned")
.insert(key.clone(), v.clone());
v
}
pub fn get(&self, key: &K) -> Option<V> {
self.map()
.lock()
.expect("OnceMap mutex poisoned")
.get(key)
.cloned()
}
pub fn insert(&self, key: K, value: V) -> Option<V> {
self.map()
.lock()
.expect("OnceMap mutex poisoned")
.insert(key, value)
}
pub fn remove(&self, key: &K) -> Option<V> {
self.map()
.lock()
.expect("OnceMap mutex poisoned")
.remove(key)
}
}
impl<K, V> Default for OnceMap<K, V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn computes_once_per_key() {
let map: OnceMap<String, i32> = OnceMap::new();
let mut calls = 0;
let mut init = |v: i32| {
calls += 1;
v
};
let a = map.get_or_init_with(&"a".to_string(), || init(1));
let a_again = map.get_or_init_with(&"a".to_string(), || init(2));
let b = map.get_or_init_with(&"b".to_string(), || init(3));
assert_eq!(a, 1);
assert_eq!(a_again, 1);
assert_eq!(b, 3);
assert_eq!(calls, 2);
}
#[test]
fn get_returns_none_when_missing() {
let map: OnceMap<String, i32> = OnceMap::new();
assert!(map.get(&"missing".to_string()).is_none());
map.insert("present".to_string(), 7);
assert_eq!(map.get(&"present".to_string()), Some(7));
}
#[tokio::test]
async fn async_init_caches_value() {
let map: OnceMap<String, i32> = OnceMap::new();
let v = map
.get_or_init_async(&"k".to_string(), || async { 42 })
.await;
assert_eq!(v, 42);
assert_eq!(map.get(&"k".to_string()), Some(42));
}
}