Somewhat tricky static HashMap

c.f.

For context, I'm trying to parse a string using a zero-copy Nom parser into parts, and then use those parts to render out other web-related strings. e.g. matchit route configs, URITemplates and regular expressions to represent URLs and interpret them. I would like, therefore, to have the Map struct own the template strings, and cache the parses for them. That works fine until I try to access those parsed values (the InnerSingle struct) because I can't return a value that references part of the local MutexGuard. The Arcs are an effort to satisfy those ownership requirements.

It seems like I must be missing something, since surely getting values out of a mutex is a thing you'd want to do.

In part, I'm trying to expand my understanding of Rust memory management, which is about half the reason I'm trying to stick to references to heap allocated values rather than simply cloning everything.

Surely this is a thing that people do, and I've just missed a trick, right?

You never want this.

impl<'a> Map<'a> {
    //       vvvvvvvvvvvv self: &'a mut Map<'a>
    fn named(&'a mut self, rt: impl RouteTemplate) -> Result<Arc<RwLock<InnerSingle>>, String> {

I would like, therefore, to have the Map struct own the template strings, and cache the parses for them.

struct InnerSingle<'a> {
    prefix: &'a str,
    template: &'a str,
}

That would be a self-referencial struct. Probably chasing compiler errors trying to attempt one is what let you to &'a mut self.

Also, the only way you can get a borrowing InnerSingle<'static> that would be possible to store in a static would be if you got a &'static to the HashMap. Translation: the HashMap is shared-borrowed forever, so you can't get any exclusive references to it again. Perhaps possible if you leaked the MutexGuard after filling it up and then made all your borrows... but I didn't attempt it. (And might as well leak the HashMap instead, probably.)

More generally you can't modify the HashMap while any shared borrows into it exist -- the HashMap could reallocate and make your references dangle, for example.[1]


  1. It would be UB due to aliasing and so on as well; the allocation example is just shorthand for "see how the compiler couldn't safely allow this"? ↩ī¸Ž

2 Likes

Like HashMap in std::collections - Rust ?

That's what led to my own &mut self - because I want to cache those strings in the Map struct.

It's not possible with safe Rust, and somewhere between extremely difficult and impossible to do without UB using unsafe, depending on exactly what you wish you could do.

You need a different approach. Maybe some sort of arena or interning, maybe some non-borrowing way to store keys and substring spans, maybe giving up on global state (especially mutable global state).

Maybe building the HashMap first, leaking it, and then creating all your &'static str to stash in a (separate) static.

Creating references while still building the HashMap won't work; references are not the correct tool for that approach.

If you're sharing immutable Strings you can use Arc<String> or Arc<str> as the unit of sharing. Or for more flexibility (e.g., sharing slices of Strings), arcstr may be useful.

1 Like