Borrow checker stopping update to HashMap cache

I am attempting to create a cache of user ids to usernames, stored in a HashMap which starts empty. As requests are made to lookup the username in this HashMap cache if it doesn't exist it gets fetched from the credentials store (in our case, pgs_files lookup to /etc/passwd but removed from this code for clarity) and stored in the cache for the next time. I am trying to update the HashMap from a self reference, and the borrow checker has berated me into submission. I have tried changing around muts, selfs and passing copies, but keep bumping into errors.

src/main.rs:15:25: 15:36 error: cannot borrow `self.mapper` as mutable because it is also borrowed as immutable [E0502]
src/main.rs:15                         self.mapper.insert(key.to_owned(), name.to_owned());

I would be grateful for any information on where I am going wrong in the code below.

Thank you!

use std::collections::HashMap;

type UidGidHashMap = HashMap<u32, String>;

struct UidGidHash { mapper: UidGidHashMap }

impl UidGidHash {

    fn search(&mut self, key: u32) -> String {

        let username: String = match self.mapper.get(&key) {
            Some(username) => username.to_string(),
            None => {
                        let name = lookup_user(key);
                        self.mapper.insert(key.to_owned(), name.to_owned());
                        return name
            }

        };
        username.to_owned()

    }

}

fn main() {

    // Gets "Jenny" - a direct lookup is working fine
    let mut uid = 503;
    let myuser = lookup_user(uid);
    println!("{:?}", myuser);

    // Create struct with hashmap of uids->usernames
    let mut table = UidGidHash { mapper: UidGidHashMap::new() };

    // Look for Jenny in the cache. As it is empty, insert the K:V pair, getting V from lookup_user(uid: u32)
    uid = 503;
    let mut user = table.search(uid);
    println!("User is {:?}", user);


}


fn lookup_user(uid: u32) -> String {
    let user = match uid {
        0 => "root",
        502 => "Matt",
        503 => "Jenny",
        _ => "UNKNOWN!"
    };
    user.to_string()
}

The borrow in self.mapper.get(&key) lasts for the whole match expression, since it's extended through the returned value. The compiler isn't clever enough to understand that None is a static value, so the above approach won't work. This is where you would want to use the entry API, which borrows the map mutably, from the start, and keeps the reference in the returned Entry. It allows you to read+manipulate or insert a value, depending on if it exists or not.

2 Likes

@ogeon Thank you very much! :relieved: I have made the changes to the impl's search and it works a treat. Below is the updated code for anyone who might find this useful!

use std::collections::hash_map::{HashMap, Entry};

type UidGidHashMap = HashMap<u32, String>;

struct UidGidHash { mapper: UidGidHashMap }

impl UidGidHash {

    fn search(&mut self, key: u32) -> String {
        match self.mapper.entry(key.to_owned()) {
            // Cache miss - lookup user_name and store
            Entry::Vacant(entry) => {
                let user_name = lookup_user(key);
                entry.insert(user_name.to_owned());
                return user_name
            }
            // Cache hit - return value
            Entry::Occupied(entry) => {
                return entry.get().to_owned()
            }
        }
    }

}


fn main() {

    // Gets "Jenny" - a direct lookup is working fine
    let mut uid = 503;
    let myuser = lookup_user(uid);
    println!("{:?}", myuser);

    // Create struct with hashmap of uids->usernames
    let mut table = UidGidHash { mapper: UidGidHashMap::new() };

    // Look for Jenny in the cache. As it is empty, insert the K:V pair, getting V from lookup_user(uid: u32)
    uid = 503;
    let user = table.search(uid);
    println!("User is {:?}", user);

    uid = 502;
    let user = table.search(uid);

    println!("HashMap contains:\n{:#?}", table.mapper);

}


fn lookup_user(uid: u32) -> String {
    let user = match uid {
        0 => "root",
        502 => "Matt",
        503 => "Jenny",
        _ => "UNKNOWN!"
    };
    user.to_string()
}