Yet another fight with the borrow checker

โš“ rust    ๐Ÿ“… 2025-07-03    ๐Ÿ‘ค surdeus    ๐Ÿ‘๏ธ 3      

surdeus

I ran into (almost exactly) the 1st problem in this article: Four limitations of Rustโ€™s borrow checker | Considerations on Codecrafting

let's reiterate the problem in this simple example:

use std::collections::HashMap;

struct Cache(HashMap<u32, String>);

impl Cache {
    fn get(&mut self, k: &u32) -> &String {
        if let Some(v) = self.0.get(k) {
            return v;
        }
        self.0.insert(*k, k.to_string());
        self.0.get(k).unwrap()
    }
}

see it in Rust Playground
it won't compile:

error[E0502]: cannot borrow `self.0` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:9
   |
6  |     fn get(&mut self, k: &u32) -> &String {
   |            - let's call the lifetime of this reference `'1`
7  |         if let Some(v) = self.0.get(k) {
   |                          ------ immutable borrow occurs here
8  |             return v;
   |                    - returning this value requires that `self.0` is borrowed for `'1`
9  |         }
10 |         self.0.insert(*k, k.to_string());
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.

clearly borrow checker is at fault here, that's a known fact.

I know refactoring it using HashMap::entry works, like this:

use std::collections::HashMap;

struct Cache(HashMap<u32, String>);

impl Cache {
    fn get(&mut self, k: &u32) -> &String {
        self.0.entry(*k).or_insert_with(|| k.to_string() )
    }
}

see it in Rust Playground
In a way, this is better since it only performs one lookup.

But it's not very flexible, what if I want something a little more complex? like I want to check if a fallback entry exist:

use std::collections::HashMap;

struct Cache(HashMap<u32, String>);

impl Cache {
    fn get(&mut self, k: &u32) -> &String {
        if let Some(v) = self.0.get(k) {
            return v;
        }
        if let Some(v) = self.0.get(&(*k + 42)) {
            return v;
        }
        self.0.insert(*k, k.to_string());
        self.0.get(k).unwrap()
    }
}

fn main() { }

I don't see a way to fix this cleanly.

Another grunt I have is that .entry() wants an owned key, it would be better if it could take a borrow and only calls .to_owned() when required.

1 post - 1 participant

Read full topic

๐Ÿท๏ธ rust_feed