Search code examples
rustborrow-checker

How can I use long-lived (near-static) borrows in Rust?


In my application, I fetch large objects (expensive), and store them in giant HashMap. Once inserted in the map, they're never removed.

The objects themselves have interior-mutability (Mutex, etc), so I never reference them as mutable. These objects are treated as singletons, once loaded, everywhere in the app references the same underlying object so that changes are global.

Pseudo-code, but the general idea is:

struct Thing {
  inner: Arc<InnerThing>,
}
struct InnerThing {
  field1: xxx,
  field2: Vec<yyy>,
  field3: Mutex<Something>,
}

struct App {
  map: HashMap<key, Thing>
}
impl App {
  fn get_thing(&mut self, key) -> Thing {
    if let Some(val) = self.map.get(key) {
      return val.clone();
    }

    // Load (expensive) Thing, insert into map
    let thing = LoadExpensiveThing();
    self.map.insert(key, thing.clone());
    thing
  }
}

Places throughout the app call getThing(key), and all interact with the same underlying Thing.

I'd love to avoid using an Arc<>, since App.map owns each Thing object, and they'll never be deleted from the map due to the use-case, but I'm not sure how to represent that to the borrow checker. Obviously I run into the normal "Doesn't live long enough", since in theory it could be deleted from the map (but won't be). I'm also using Tokio, so these references need to be passed to spawned tasks (Sync/Send).

How can I tell the borrow checker that the references will live basically forever (essentially 'static as far as it should care)?


Solution

  • How can I tell the borrow checker that the references will live basically forever (essentially 'static as far as it should care)?

    You don't, because they are not 'static. Lying to the compiler about lifetimes is a fantastic way to cause undefined behavior. (You can do it with unsafe but you absolutely should not in this case.)

    Just because the items will not be removed from the map doesn't mean they will always exist at the same location in memory. Adding items to the map can trigger a reallocation that can relocate existing values, which would invalidate all existing references to them.

    Rust is protecting you here. Some other languages would let you do this, causing undefined behavior at some later time.

    Just use Arc. Optimizing this in advance is a bad idea; the overhead of Arc is extremely unlikely to be a bottleneck.