Partial borrowing


#1

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?


#2

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)
}

#3

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.


#4

If operate took a mutable reference would it be okay?


#5

&mut self? no
for the same reasons
it is all about reference that is kept in the local state variable, not about the borrowing &self

you can check yourself
https://is.gd/6Og9IV


#6

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