Static reference to generic default value

I am not good at English. Sorry if there are any funny expressions.


How can I get a static reference to generic default value?

I wrote the code described below and confirmed that it worked for now.
However, it has dozens of lines and includes unsafe.
I almost always fail with unsafe.

If this is possible, I can avoid using multiple memories for the same default values, as in the MyMap code below. (I believe this is a natural use. ...Right?)


Key part is DefaultMap::get::<T>().

use std::any::{Any, TypeId};
use std::collections::BTreeMap;
use std::sync::{LazyLock, Mutex, MutexGuard};

#[test]
fn usecase() {
    let mut map = MyMap::<i32>::default();
    map.insert(1, 1);
    map.insert(2, 2);
    assert_eq!(map.get(&0), &0);
    assert_eq!(map.get(&1), &1);
    assert_eq!(map.get(&2), &2);
    assert_eq!(map.get(&3), &0);

    #[derive(Default)]
    struct MyMap<T> {
        base: BTreeMap<usize, T>,
    }

    impl<T> MyMap<T>
    where
        T: Any + Default + Send + Sync,
    {
        pub fn get(&self, key: &usize) -> &T {
            self.base
                .get(key)
                .unwrap_or_else(|| DefaultMap::get::<T>())
        }

        pub fn insert(&mut self, key: usize, value: T) {
            self.base.insert(key, value);
        }
    }
}

static SINGLETON: LazyLock<Mutex<DefaultMap>> = LazyLock::new(|| Mutex::new(DefaultMap::new()));

pub struct DefaultMap(BTreeMap<TypeId, Box<dyn Any + Send + Sync>>);

impl DefaultMap {
    pub fn get<T>() -> &'static T
    where
        T: Any + Default + Send + Sync,
    {
        let mut instance = Self::instance();
        let ret = instance.get_or_insert::<T>();
        unsafe { &*(ret as *const _) }
    }

    fn new() -> Self {
        Self(BTreeMap::new())
    }

    fn instance() -> MutexGuard<'static, Self> {
        LazyLock::force(&SINGLETON).lock().unwrap()
    }

    fn get_or_insert<T>(&mut self) -> &T
    where
        T: Any + Default + Send + Sync,
    {
        self.0
            .entry(TypeId::of::<T>())
            .or_insert_with(|| Box::new(T::default()))
            .downcast_ref::<T>()
            .unwrap()
    }
}

You can and should replace the unsafe code (which is unsound, because BTreeMap doesn't promise not to move its contents, and you took an exclusive reference to it) with Box::leak().

pub fn default_ref<T>() -> &'static T
where
    T: Any + Default + Send + Sync,
{
    static MAP: Mutex<BTreeMap<TypeId, &'static (dyn Any + Send + Sync)>> =
        Mutex::new(BTreeMap::new());

    MAP.lock()
        .unwrap()
        .entry(TypeId::of::<T>())
        .or_insert_with(|| Box::leak(Box::new(T::default())))
        .downcast_ref()
        .unwrap()
}

But you could also avoid shared static state entirely with a default value stored in MyMap itself:

    #[derive(Default)]
    struct MyMap<T> {
        base: BTreeMap<usize, T>,
        default: T,
    }

    impl<T> MyMap<T>
    where
        T: Any + Default + Send + Sync,
    {
        pub fn get(&self, key: &usize) -> &T {
            self.base.get(key).unwrap_or(&self.default)
        }
        ...

Of course, that duplicates the default value for each instance of MyMap, but that may be preferable to the bottleneck of accessing a single static mutex. (You could also improve on that by using a concurrent map type instead of Mutex<BTreeMap>.)

2 Likes

Ah, indeed... When I add a subsequent entry to BTreeMap, the location of the existing entries may change at that point... (Also, I didn't know that Box::leak could be used in this way.)

About adding default fields to MyMap. It's simple and great. Ah... What was I doing?

Anyway, thank you very much.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.