Single Thread Static Collection with no Sync

I want to declare a static hashmap with values not implementing Sync trait. The reason is that I want the static hashmap being accessed only by a single thread. The value is actually a Rc wrapped. I am following the below crates mentioned in: hashmap - Rust best practices when specifying a constant hash map - Stack Overflow but they give me error that my value does not implement Sync trait.
Is there any way where I can define a static hashmap with values not implementing Sync trait ?

Maybe you find this topic interesting:

2 Likes

A couple of approaches you can try:

4 Likes

ohh thank you so much @jofas for your quick response. Let me read through the post and check if this solves my problem. Thanks again :grin:

Thanks for your answer @2e71828 . Let me try out with both the approaches and revert back here if I face any issues. Thanks again :grin:

@2e71828 I can define a static hashmap using thread_local but can I have a static ref to it ? something like

thread_local! {
    static BUILTIN_METHODS: HashMap<&'static str, Rc<usize>> = HashMap::new();
}

fn get_builtin_methods(&self) -> &'static HashMap<&'static str, Rc<usize>> {
    todo!()
}

You can't get a static reference to a thread local because it's not a "true" static reference due to not persisting after the thread that owns it stops. You can work around this by leaking a Box and storing the resulting &'static reference into the thread local (see the post linked above for an example).

2 Likes

so @SkiFire13 something like this

thread_local!(
    static BUILTIN_METHODS: &'static HashMap<&'static str, Rc<usize>> =
        Box::leak(Box::new(HashMap::from([
            ("append", Rc::new(20)),
        ])))
);

fn get_builtin_methods(&self) -> &'static HashMap<&'static str, Rc<usize>> {
        BUILTIN_METHODS.with(|use_default| *use_default)
    }

FWIW, in Rust this is an antipattern. Actually, come to think of it, it is an antipattern in languages like Java too.

I suspect you want this to save yourself an argument in fn/method calls. If so, please don't do this. It will likely cause code around it to be rather brittle, or at least brittle in the face of new changes. Static collections often are.
In fact the thread so far already demonstrates how kludge is stacked upon kludge to make something workable that was never intended to be used this way in the first place :slight_smile:

5 Likes

@jjpe now come to think about this, it surely looks like an anti-pattern coz the solution does not look that natural given how simple the problem is. I think I would go with making it use up function calls.

I saw somebody linked my topic, I'm here to see what happening. :thinking:

How about creating a new struct?

use std::collections::HashMap;
use std::rc::Rc;

thread_local! {
    static BUILTIN_METHODS_BACKEND: HashMap<&'static str, Rc<usize>> = {
        let mut map = HashMap::new();
        map.insert("test", Rc::new(10));
        map
    };
}

static BUILTIN_METHODS: MyHashMap = MyHashMap;

struct MyHashMap;

impl MyHashMap {
    fn get(&self, key: &str) -> Option<Rc<usize>> {
        BUILTIN_METHODS_BACKEND.with(|map| map.get(key).cloned())
    }
}

fn main(){
    dbg!(BUILTIN_METHODS.get("test"));
}

That doesn't really solve anything:

  • The data inside the hashmap (as well as the hashmap itself) is still thread-local, limiting the viable access patterns to that data
  • The problematic pattern of using a global is still present, so the code is still brittle

Personally I'd just create a regular map (hashmap or otherwise), fill it, and then just borrow that where needed. It looks a bit more verbose but it's way more robust and flexible.

2 Likes

Isn't the data itself thread-local? If I understand correctly, the data(Rc) don't implement Send, so the data doomed cannot be accessed by other threads.

Indeed, this workaround is just a wrapper, but is this not what he wants, a static map that gives Rcs?

Uh, if cosider the practical use, I would suggest closures.

hey @Fancyflame thank you for your response. Actually Rc<usize> was just an example to point out that value in hashmap is not Sync trait implemented, but in my real code it has some bunch of vecs of Rc and I really don't want to clone the vector or wrap it inside Rc. I really want to have a static reference to the entries of the static hashmap.

Only !Sync or also !Send? If only !Sync, consider wrap those vecs into mutexes, take it easy that don't cause heap allocation. But if also !Send, you can never get a static hashmap except you leak the memory, but the side effect exactly is the memory leaked.
Cloning the value is also an idea which you turned down before.
Besides, Rc<HashMap<_, _>> is also good. No memory leak, no Send needed, everything is fine.

Think twice on whether the thing you finally get must be a static reference, not anything that can de-reference to hashmaps. If above workaround doesn't suitable to your situation, and what you need is exactly, really and uncompromisingly the static reference of hashmaps, may I ask the practical use case? I'm interested and curious.(seriously)

Actually I am developing my own toy programming language in which there are types like list and dictionaries. There are bounded methods assosiated with those types. Now whenever I encounter a syntax like this x.append(2), and if the type of x is let's say List<int> I want to search the method append in a static hashmap of builtin methods associated with type List<int> and get a static ref to the method signature like {params: [T], return_type: <void>}. Having a static hashmap makes sense as the builtin methods are sort of fixed for a particular non-struct types like list and dictionaries.
Also I don't see any problem in leaking the memory to make this hashmap available for the lifetime of the program as that's what I want. But maybe you all are correct, that I would just require this kind of hashmap in just type-checking phase and then required to be dropped but using the static ref I won't be able to do it which is also a sensible argument. But still static ref to hashmap looks so elegant :sweat_smile:

If you are going to create new threads in which memory leaked, then more threads created more memory leaked no matter whether they exited, and if that happens in your language runtime it will cause a large memory occupation during the process's lifetime. But if you're sure your process will only run on a single thread of course you can.

But the problem is, according to the usage you said, I see nothing requires that it must not be shared with other threads, so what type is it? If it's Rc you can use Arc instead. Try to make full use of things under std::sync module.

My situation is syn::Expr contains TokenStream, which is not Send, and I'm going to design a function that takes an optioned reference, if it's Some returns the inner value then returns a global value if None. The type of the global value needs to be equivalent to the inner value, so I need a static reference. But finally I decided to sacrifice such a little little performance overhead cloning that thing and convinced myself not to make that function, to make myself comfortable in coding.

2 Likes

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.