There is something that I continuously stumble with. I think it is better to show with an example:
use std::fmt::Debug;
use std::collections::HashMap;
pub struct MyStruct<T> {
states: HashMap<i8, T>,
}
impl<T: Default + Debug> MyStruct<T> {
pub fn do_something(&mut self, id: i8) {
// get id
let state = {
self.states.entry(id).or_insert(T::default())
};
let out = self.operate(state);
}
pub fn operate(&self, state: &mut T) {
println!("Here {:?}", state);
}
}
fn main() {
let mut p : MyStruct<i32> = MyStruct { states: HashMap::new()};
p.do_something(4)
}
This is the type of situations in which it seems that the borrow checker if fighting against me instead of helping me. But is clear that I am missing something. What are the best practices to deal with this type of situations?
Hi, i think the borrow checking does exactly what it should
You try to borrow self as immutable while holding a mutable reference inside the state variable
If you don't want to access self structure inside operate, you can make it static
use std::fmt::Debug;
use std::collections::HashMap;
pub struct MyStruct<T> {
states: HashMap<i8, T>,
}
impl<T: Default + Debug> MyStruct<T> {
pub fn do_something(&mut self, id: i8) {
// get id
let state = {
self.states.entry(id).or_insert(T::default())
};
let out = MyStruct::operate(state);
}
pub fn operate(state: &mut T) {
println!("Here {:?}", state);
}
}
fn main() {
let mut p : MyStruct<i32> = MyStruct { states: HashMap::new()};
p.do_something(4)
}
If you want to access self as immutable, then you can not, since you hold the mutable reference to it, and your option is to use RefCell for "partial borrowing" (interior mutability)
use std::fmt::Debug;
use std::collections::HashMap;
use std::cell::RefCell;
pub struct MyStruct<T> {
states: RefCell<HashMap<i8, T>>,
}
impl<T: Default + Debug> MyStruct<T> {
pub fn do_something(&mut self, id: i8) {
let mut states = self.states.borrow_mut();
// get id
let state = {
states.entry(id).or_insert(T::default())
};
let out = self.operate(state);
}
pub fn operate(&self, state: &mut T) {
println!("Here {:?}", state);
}
}
fn main() {
let mut p : MyStruct<i32> = MyStruct { states: RefCell::new(HashMap::new())};
p.do_something(4)
}
Thanks for your reply. Making the function static is a good idea. I cannot use it in my particular problem but I will keep it mind for the future.
Using RefCell worked perfectly. Thanks! I do have a question though. Is there a significant performance penalty of using RefCell that precludes it usage in certain type of applications?
I guess that what I am looking is a way to say that operate can touch anything except states. But probably that will be too fine grained and make the syntax enormously complicated, and I am not sure if it will bring any value.
Cannot answer this question about perfrmance
You can compare two variants of compiled code at https://play.rust-lang.org/ or make some benchmarks
Never had an opportunity to do it myself