use std::{
any::{Any, TypeId},
collections::HashMap,
fmt,
hash::{BuildHasherDefault, Hasher},
sync::{Arc, OnceLock, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
type Slots = HashMap<TypeId, Arc<dyn Slot>, BuildHasherDefault<TypeIdHasher>>;
#[derive(Default)]
pub struct OnceTypeMap {
slots: RwLock<Slots>,
}
impl OnceTypeMap {
pub fn new() -> Self {
Self::default()
}
pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.slot::<T>()?.get()
}
pub fn get_or_init<T: Send + Sync + 'static>(&self, init: impl FnOnce() -> T) -> &T {
self.slot_or_create::<T>().get_or_init(init)
}
pub fn insert<T: Send + Sync + 'static>(&self, value: T) -> Result<&T, T> {
let slot = self.slot_or_create::<T>();
slot.set(value)?;
Ok(slot.get().expect("value was just set"))
}
pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
self.get::<T>().is_some()
}
pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
let slot = self.slots_mut().get_mut(&TypeId::of::<T>())?;
let slot = Arc::get_mut(slot).expect(SLOT_NEVER_SHARED);
downcast_slot_mut::<T>(slot).get_mut()
}
pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
let slot = self.slots_mut().remove(&TypeId::of::<T>())?;
let slot = downcast_slot_arc::<T>(slot);
Arc::into_inner(slot).expect(SLOT_NEVER_SHARED).into_inner()
}
pub fn clear(&mut self) {
self.slots_mut().clear();
}
pub fn len(&self) -> usize {
self.read().values().filter(|slot| slot.is_set()).count()
}
pub fn is_empty(&self) -> bool {
!self.read().values().any(|slot| slot.is_set())
}
fn slot<T: Send + Sync + 'static>(&self) -> Option<&OnceLock<T>> {
let slots = self.read();
let slot = downcast_slot::<T>(&**slots.get(&TypeId::of::<T>())?);
Some(unsafe { self.detach(slot) })
}
fn slot_or_create<T: Send + Sync + 'static>(&self) -> &OnceLock<T> {
if let Some(slot) = self.slot::<T>() {
return slot;
}
self.create_slot::<T>()
}
fn create_slot<T: Send + Sync + 'static>(&self) -> &OnceLock<T> {
let mut slots = self.write();
let slot = slots.entry(TypeId::of::<T>()).or_insert_with(new_slot::<T>);
let slot = downcast_slot::<T>(&**slot);
unsafe { self.detach(slot) }
}
unsafe fn detach<'a, T>(&'a self, slot: &OnceLock<T>) -> &'a OnceLock<T> {
unsafe { &*(slot as *const OnceLock<T>) }
}
fn read(&self) -> RwLockReadGuard<'_, Slots> {
self.slots.read().unwrap_or_else(PoisonError::into_inner)
}
fn write(&self) -> RwLockWriteGuard<'_, Slots> {
self.slots.write().unwrap_or_else(PoisonError::into_inner)
}
fn slots_mut(&mut self) -> &mut Slots {
self.slots.get_mut().unwrap_or_else(PoisonError::into_inner)
}
}
impl fmt::Debug for OnceTypeMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OnceTypeMap")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
const SLOT_TYPE_MISMATCH: &str = "slot type always matches its TypeId key";
const SLOT_NEVER_SHARED: &str = "slot Arcs are never cloned";
fn new_slot<T: Send + Sync + 'static>() -> Arc<dyn Slot> {
Arc::new(OnceLock::<T>::new())
}
fn downcast_slot<T: Send + Sync + 'static>(slot: &dyn Slot) -> &OnceLock<T> {
let slot: &dyn Any = slot;
slot.downcast_ref().expect(SLOT_TYPE_MISMATCH)
}
fn downcast_slot_mut<T: Send + Sync + 'static>(slot: &mut dyn Slot) -> &mut OnceLock<T> {
let slot: &mut dyn Any = slot;
slot.downcast_mut().expect(SLOT_TYPE_MISMATCH)
}
fn downcast_slot_arc<T: Send + Sync + 'static>(slot: Arc<dyn Slot>) -> Arc<OnceLock<T>> {
let slot: Arc<dyn Any + Send + Sync> = slot;
slot.downcast().expect(SLOT_TYPE_MISMATCH)
}
trait Slot: Any + Send + Sync {
fn is_set(&self) -> bool;
}
impl<T: Send + Sync + 'static> Slot for OnceLock<T> {
fn is_set(&self) -> bool {
self.get().is_some()
}
}
#[derive(Default)]
struct TypeIdHasher(u64);
impl Hasher for TypeIdHasher {
fn write(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.0 = self.0.rotate_left(8) ^ u64::from(byte);
}
}
fn write_u64(&mut self, n: u64) {
self.0 ^= n;
}
fn finish(&self) -> u64 {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
use std::time::Duration;
#[test]
fn is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<OnceTypeMap>();
}
#[test]
fn get_or_init_then_get() {
let map = OnceTypeMap::new();
assert_eq!(map.get::<i32>(), None);
assert_eq!(*map.get_or_init(|| 42i32), 42);
assert_eq!(map.get::<i32>(), Some(&42));
assert!(map.contains::<i32>());
assert!(!map.contains::<u32>());
}
#[test]
fn multiple_types_are_independent() {
let map = OnceTypeMap::new();
map.get_or_init(|| 42i32);
map.get_or_init(|| "hello".to_string());
map.get_or_init(|| 2.5f64);
assert_eq!(map.len(), 3);
assert_eq!(map.get::<i32>(), Some(&42));
assert_eq!(map.get::<String>().map(String::as_str), Some("hello"));
assert_eq!(map.get::<f64>(), Some(&2.5));
}
#[test]
fn initializer_does_not_run_when_set() {
let map = OnceTypeMap::new();
let calls = AtomicUsize::new(0);
for _ in 0..3 {
let value = map.get_or_init(|| {
calls.fetch_add(1, Ordering::SeqCst);
42i32
});
assert_eq!(*value, 42);
}
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn references_point_to_the_same_value() {
let map = OnceTypeMap::new();
let a = map.get_or_init(|| 42i32);
let b = map.get::<i32>().unwrap();
let c = map.get_or_init::<i32>(|| unreachable!());
assert!(std::ptr::eq(a, b));
assert!(std::ptr::eq(b, c));
}
#[test]
fn insert_sets_once_and_returns_rejected_value() {
let map = OnceTypeMap::new();
assert_eq!(map.insert(1u32), Ok(&1));
assert_eq!(map.insert(2u32), Err(2));
assert_eq!(*map.get_or_init(|| 3u32), 1);
}
#[test]
fn panicking_initializer_leaves_value_unset() {
let map = OnceTypeMap::new();
let result = catch_unwind(AssertUnwindSafe(|| {
map.get_or_init::<i32>(|| panic!("init failed"));
}));
assert!(result.is_err());
assert_eq!(map.get::<i32>(), None);
assert_eq!(map.len(), 0);
assert!(map.is_empty());
assert_eq!(*map.get_or_init(|| 42i32), 42);
}
#[test]
fn insert_after_panicking_initializer() {
let map = OnceTypeMap::new();
let _ = catch_unwind(AssertUnwindSafe(|| {
map.get_or_init::<u8>(|| panic!("init failed"));
}));
assert_eq!(map.insert(5u8), Ok(&5));
}
#[test]
fn remove_and_clear_free_empty_slots() {
let mut map = OnceTypeMap::new();
for _ in 0..2 {
let _ = catch_unwind(AssertUnwindSafe(|| {
map.get_or_init::<i32>(|| panic!("init failed"));
}));
}
let _ = catch_unwind(AssertUnwindSafe(|| {
map.get_or_init::<u8>(|| panic!("init failed"));
}));
assert_eq!(map.slots_mut().len(), 2);
assert!(map.is_empty());
assert_eq!(map.remove::<i32>(), None);
assert_eq!(map.slots_mut().len(), 1);
map.clear();
assert_eq!(map.slots_mut().len(), 0);
}
#[test]
fn initializer_can_use_other_types() {
let map = OnceTypeMap::new();
let outer = map.get_or_init(|| {
let inner: &String = map.get_or_init(|| "inner".to_string());
inner.len()
});
assert_eq!(*outer, 5);
assert_eq!(map.get::<String>().map(String::as_str), Some("inner"));
}
struct Marker<const N: usize>;
macro_rules! fill {
($map:expr, $($n:literal)*) => { $( $map.get_or_init(|| Marker::<$n>); )* };
}
#[test]
fn references_survive_map_growth() {
let map = OnceTypeMap::new();
let pinned: &String = map.get_or_init(|| "pinned".to_string());
let address = pinned as *const String;
fill!(map, 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39);
assert_eq!(map.len(), 41);
assert_eq!(pinned, "pinned");
assert!(std::ptr::eq(map.get::<String>().unwrap(), address));
}
#[test]
fn create_slot_on_occupied_entry_keeps_references_valid() {
let map = OnceTypeMap::new();
let held: &String = map.get_or_init(|| "held".to_string());
let again = map.create_slot::<String>();
assert_eq!(held, "held");
assert!(std::ptr::eq(again.get().unwrap(), held));
map.create_slot::<String>().get_or_init(|| unreachable!());
assert_eq!(held, "held");
}
#[test]
fn references_survive_typed_moves_of_slots() {
#[inline(never)]
fn pass_through<S>(slot: S) -> S {
slot
}
let map = OnceTypeMap::new();
let held: &String = map.get_or_init(|| "held".to_string());
map.get_or_init(|| 1u8);
{
let mut slots = map.write();
let moved: Vec<_> = slots.drain().collect();
for (id, slot) in moved {
slots.insert(id, pass_through(slot));
}
}
assert_eq!(held, "held");
assert!(std::ptr::eq(map.get::<String>().unwrap(), held));
}
#[test]
fn zero_sized_values() {
let mut map = OnceTypeMap::new();
fill!(map, 0 1);
assert!(map.get::<Marker<0>>().is_some());
assert!(map.get_mut::<Marker<1>>().is_some());
assert!(map.remove::<Marker<0>>().is_some());
assert!(map.get::<Marker<0>>().is_none());
assert_eq!(map.len(), 1);
}
#[test]
fn type_id_hashes_through_write_u64() {
use std::hash::Hash;
#[derive(Default)]
struct OnlyWriteU64;
impl Hasher for OnlyWriteU64 {
fn write(&mut self, _: &[u8]) {
panic!("TypeId no longer hashes through write_u64");
}
fn write_u64(&mut self, _: u64) {}
fn finish(&self) -> u64 {
0
}
}
TypeId::of::<String>().hash(&mut OnlyWriteU64);
}
#[test]
fn get_mut_remove_and_clear() {
let mut map = OnceTypeMap::new();
map.get_or_init(|| vec![1, 2]);
map.get_or_init(|| 7u8);
map.get_mut::<Vec<i32>>().unwrap().push(3);
assert_eq!(map.get::<Vec<i32>>(), Some(&vec![1, 2, 3]));
assert_eq!(map.get_mut::<u16>(), None);
assert_eq!(map.remove::<Vec<i32>>(), Some(vec![1, 2, 3]));
assert_eq!(map.remove::<Vec<i32>>(), None);
assert_eq!(map.len(), 1);
map.clear();
assert!(map.is_empty());
assert_eq!(*map.get_or_init(|| 9u8), 9);
}
#[test]
fn values_are_dropped() {
let tracker = Arc::new(());
let mut map = OnceTypeMap::new();
map.get_or_init(|| Arc::clone(&tracker));
assert_eq!(Arc::strong_count(&tracker), 2);
map.clear();
assert_eq!(Arc::strong_count(&tracker), 1);
map.get_or_init(|| Arc::clone(&tracker));
drop(map);
assert_eq!(Arc::strong_count(&tracker), 1);
}
#[test]
fn recovers_from_poisoned_lock() {
let map = OnceTypeMap::new();
map.get_or_init(|| 1i32);
let _ = catch_unwind(AssertUnwindSafe(|| {
let _guard = map.slots.write().unwrap();
panic!("poison the lock");
}));
assert!(map.slots.is_poisoned());
assert_eq!(map.get::<i32>(), Some(&1));
assert_eq!(*map.get_or_init(|| 2u8), 2);
assert_eq!(map.len(), 2);
let mut map = map;
*map.get_mut::<i32>().unwrap() += 1;
assert_eq!(map.remove::<i32>(), Some(2));
map.clear();
assert!(map.is_empty());
}
#[test]
fn waiter_initializes_after_concurrent_initializer_panics() {
let map = OnceTypeMap::new();
let (started_tx, started_rx) = std::sync::mpsc::channel();
thread::scope(|s| {
let panicking = s.spawn(|| {
map.get_or_init::<u32>(|| {
started_tx.send(()).unwrap();
thread::sleep(Duration::from_millis(50));
panic!("init failed");
});
});
started_rx.recv().unwrap();
assert_eq!(*map.get_or_init(|| 7u32), 7);
assert!(panicking.join().is_err());
});
assert_eq!(map.get::<u32>(), Some(&7));
}
#[test]
fn concurrent_initializer_runs_exactly_once() {
const THREADS: usize = 8;
let rounds = if cfg!(miri) { 2 } else { 100 };
for _ in 0..rounds {
let map = Arc::new(OnceTypeMap::new());
let calls = Arc::new(AtomicUsize::new(0));
let barrier = Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let (map, calls, barrier) = (map.clone(), calls.clone(), barrier.clone());
thread::spawn(move || {
barrier.wait();
*map.get_or_init(|| {
calls.fetch_add(1, Ordering::SeqCst);
thread::sleep(Duration::from_millis(1));
42u64
})
})
})
.collect();
for handle in handles {
assert_eq!(handle.join().unwrap(), 42);
}
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
}
#[test]
fn concurrent_different_types() {
let map = Arc::new(OnceTypeMap::new());
let handles: Vec<_> = (0..10)
.map(|thread_id| {
let map = Arc::clone(&map);
thread::spawn(move || match thread_id % 5 {
0 => assert_eq!(*map.get_or_init(|| 42i32), 42),
1 => assert_eq!(map.get_or_init(|| "thread".to_string()), "thread"),
2 => assert_eq!(*map.get_or_init(|| 2.5f64), 2.5),
3 => assert!(*map.get_or_init(|| true)),
_ => assert_eq!(*map.get_or_init(|| vec![1, 2, 3]), vec![1, 2, 3]),
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(map.len(), 5);
}
#[test]
fn concurrent_readers_and_writers() {
let map = Arc::new(OnceTypeMap::new());
map.get_or_init(|| "shared".to_string());
let iterations = if cfg!(miri) { 5 } else { 200 };
let handles: Vec<_> = (0..6)
.map(|thread_id| {
let map = Arc::clone(&map);
thread::spawn(move || {
let shared: &String = map.get().unwrap();
for i in 0..iterations {
match thread_id % 3 {
0 => {
map.get_or_init(|| i as u64);
}
1 => {
let _ = map.insert(i as u32);
}
_ => assert!(!map.is_empty()),
}
assert_eq!(shared, "shared");
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(map.len(), 3);
}
#[test]
fn debug_shows_len() {
let map = OnceTypeMap::new();
map.get_or_init(|| 1i32);
assert_eq!(format!("{map:?}"), "OnceTypeMap { len: 1, .. }");
}
}