Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Borrowing issues with attempted caching

Tags:

rust

The following code snippet does the same things in 3 ways.

use std::collections::HashMap;

struct Foo {
    cache: HashMap<String, String>,
}

impl Foo {
    fn get_cached(&mut self, key: &String) -> &String {
        if !self.cache.contains_key(key) {
            self.cache.insert(key.clone(), String::from("default"));
        }
        self.cache.get(key).unwrap()
    }
    fn show_impl(&self, what: &String) {
        println!("{}", what);
    }
    pub fn show1(&mut self, key: &String) {
        println!("{}", self.get_cached(key));
    }
    pub fn show2(&mut self, key: &String) {
        if !self.cache.contains_key(key) {
            self.cache.insert(key.clone(), String::from("default"));
        }
        self.show_impl(self.cache.get(key).unwrap());
    }
    // This does not compile
    pub fn show3(&mut self, key: &String) {
        self.show_impl(self.get_cached(key));
    }
}

fn main() {}

show3 doesn't compile, giving the following error:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:28:24
   |
28 |         self.show_impl(self.get_cached(key));
   |         ----           ^^^^                - immutable borrow ends here
   |         |              |
   |         |              mutable borrow occurs here
   |         immutable borrow occurs here

As far as I tell, the issue with show3 is not about mutability, but rather a double borrow: the borrow of self is given away to get_cached which doesn't end because get_cached returns a reference to something contained in self. Is this even correct?

How can I accomplish my intended goal of looking up a value in a mutable cache in self and pass the reference to another method of self?

like image 437
porgarmingduod Avatar asked Aug 07 '14 18:08

porgarmingduod


1 Answers

Rust doesn't handle this sort of caching well at the moment.

The best solution is to avoid the problem altogether. Does show_impl really need to be a method of Foo? If not, you can define a new trait and implement it for String. For example:

trait ShowImpl: std::fmt::Display {
    fn show_impl(&self) {
        println!("{}", self);
    }
}

impl ShowImpl for String {}

Then just call show_impl on the string: self.get_cached(key).show_impl();


Here is a solution that uses UnsafeCell. I'm not sure if it works correctly. While it does compile, the use of unsafe code means the compiler can no longer guarantee safety.

use std::collections::HashMap;
use std::cell::UnsafeCell;

struct Foo {
    cache: UnsafeCell<HashMap<String, String>>,
}

impl Foo {
    fn get_cached(&self, key: &String) -> &String {
        unsafe {
            if !(*self.cache.get()).contains_key(key) {
                (*self.cache.get()).insert(key.clone(), String::from("default"));
            }
            (*self.cache.get()).get(key).unwrap()
        }
    }
    fn show_impl(&self, what: &String) {
        println!("{}", what);
    }
    pub fn show1(&mut self, key: &String) {
        println!("{}", self.get_cached(key));
    }
    pub fn show2(&mut self, key: &String) {
        unsafe {
            if !(*self.cache.get()).contains_key(key) {
                (*self.cache.get()).insert(key.clone(), String::from("default"));
            }
            self.show_impl((*self.cache.get()).get(key).unwrap());
        }
    }

    pub fn show3(&self, key: &String) {
        self.show_impl(self.get_cached(key));
    }
}

fn main() {}
like image 90
A.B. Avatar answered Oct 14 '22 06:10

A.B.