Help with lifetimes and references

I have a struct with two maps, one holds Strings, and the other should only hold references.
(Original problem is more involved, but simplified for the sake of this forum)

use std::collections::HashMap;
#[derive(Default)]
struct Test<'a> {
map: HashMap<u32, String>,
reverse_map: HashMap<&'a str, u32>,
}

impl<'a> Test<'a> {
pub fn write(&'a mut self, term: String, id: u32) {
    let uid = self.addToMap(term, id);
    let tmp = self.map.get(&uid).unwrap();
    self.reverse_map.insert(tmp, uid);
}

pub fn addToMap(&'a mut self, term: String, id: u32) -> u32 {
    let id_opt = self.map.insert(id, term);
    if id_opt.is_none() {
        return id;
    } else {
        return id;
    }
}
}

Error:

 Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `self.map` as immutable because it is also borrowed as mutable
  --> src/lib.rs:11:19
   |
8  | impl<'a> Test<'a> {
   |      -- lifetime `'a` defined here
9  |     pub fn write(&'a mut self, term: String, id: u32) {
10 |         let uid = self.addToMap(term, id);
   |                   -----------------------
   |                   |
   |                   mutable borrow occurs here
   |                   argument requires that `*self` is borrowed for `'a`
11 |         let tmp = self.map.get(&uid).unwrap();
   |                   ^^^^^^^^ immutable borrow occurs here

You’re essentially trying to create a self-referential struct here (the reverse_map creating references “into” the map) field. Self-referential types like this aren’t supported by Rust at the moment (without unsafe code or certain helper macros in some crates). And even with some extra crates, they wouldn’t help you implement your type the way you want, because of the following point:

Furthermore, your current implementation is incorrect (has memory bugs [would have memory bugs if the compiler wouldn’t reject it and you wouldn’t use the &'a mut Test<'a> anti-pattern]). Your write method inserts a value into map and then inserts a reference that’s pointing into map into reverse_map. Do this multiple times, and the writes to map will invalidate the references that were previously put into reverse_map, whenever map needs to re-size. If your write method would compile and could be called multiple times, it could result in use-after-free.

An easy workaround would be to just clone the String. If you’re only “needing” the references because cloning the Strings is too expensive, you can use shared ownership through Rc or Arc to share the String between the two maps. E.g. like this (untested). Perhaps even expose the ability to use shared strings to the user. Or use a different type that’s cheap to copy such as e.g. string::String<bytes::Bytes> (actually I never actually used that one myself before, so don’t take this as a recommendation. I just recently learned these crates exist.)

2 Likes

Thanks!! Yes, I was actually using Rc earlier. I can't say I saw any performance issues, but I wanted to try using references.
I have a bunch of maps, and other data structures where these Strings will be used. So, I thought, I can store the raw String in one map, and use its reference in the others. This is for a high performance project, so I'm probably trying to over-optimzie.

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.