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?
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() {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With