Mutable borrow / Immutable Borrow / Move

Hi,

This code give me a "cannot borrow test as immutable because it is also borrowed as mutable". Even if the borrow are basically using completely different part of the struct. The compiler could be more intelligent but I understand why it gives me this error.

struct Test {
    vec: Vec<bool>,
    other: bool,
}

impl Test {
    fn print_other(&self) {
        println!("{:?}", self.other);
    }
}

fn main() {
    let mut test = Test {
        vec: vec![true, false],
        other: true,
    };

    for (_, value) in test.vec.iter_mut().enumerate() {
        *value = true;
        test.print_other();
    }
}

To solve this, here is the code I used. I basically move one part of the struct in a local variable (and I set some temp value in the struct), I borrow the local variable as mutable, call my function (which use the other part of the struct). And then, I move back in the struct the local variable. This works pretty well but this feels inaesthetic. It there a better way to do this?

struct Test {
    vec: Vec<bool>,
    other: bool,
}

impl Test {
    fn print_other(&self) {
        println!("{:?}", self.other);
    }
}

fn main() {
    let mut test = Test {
        vec: vec![true, false],
        other: true,
    };

    let mut vec = test.vec;
    test.vec = Default::default();
    for (_, value) in vec.iter_mut().enumerate() {
        *value = true;
        test.print_other();
    }
    test.vec = vec;
}

Not really, that's the standard solution. The signature of the function must completely describe the conditions to properly (from the language perspective), and your function says it borrows the whole struct, so the whole struct must be valid and borrowable. There are some discussions about letting you to write more complex signatures, specifying that you only use a part of the struct, but personally I think it would add more problems than it would solve.

It's pretty standard to take out a few fields of the struct and then put them back later, in order to satisfy the borrow checker. For example, you could have a function like

use std::mem;
fn do_stuff_with_test(t: &mut Test) {
    let v = mem::take(&mut t.vec);
    // do stuff with `t` and `v` simultaneously
    t.vec = v;
}

This specifies that the things you do with t really don't involve the inner vector.

Another option is to split the different fields into different substructs, and implement the methods which involve only parts of the fields on the relevant struct. E.g.

struct Test {
    vec: Vec<bool>,
    other: Other,
}

struct Other {
    b: bool,
}

impl Other {
    fn print_other(&self) {
        println!("{:?}", self.b);
    }
}

fn main() {
    let mut test = Test {
        vec: vec![true, false],
        other: Other {
            b: true
        },
    };

    for (_, value) in test.vec.iter_mut().enumerate() {
        *value = true;
        test.other.print_other();
    }
}

Now that you have split the parts into separate structures, the compiler can statically see that you access different fields on the parent struct in a different way.

It's not always possible to make such a split (different methods may use different field combinations), but often it works.

The last resort option is interior mutability with run-time borrow checking. You can wrap the mutably accessed fields in some struct enabling interior mutability (=mutability under a & reference), e.g. Cell, RefCell, Mutex, RwLock or something else. E.g.

use std::cell::RefCell;

struct Test {
    vec: RefCell<Vec<bool>>,
    other: bool,
}

impl Test {
    fn print_other(&self) {
        println!("{:?}", self.other);
    }
}

fn main() {
    let mut test = Test {
        vec: RefCell::new(vec![true, false]),
        other: true,
    };

    for (_, value) in test.vec.borrow_mut().iter_mut().enumerate() {
        *value = true;
        test.print_other();
    }
}

This way you delay the actual borrow checking until the execution time. You must still uphold that multiple parts of code don't mutably access the same data simultaneously, and you will get a runtime panic if you violate that condition, but you are way more flexible in the ways you can structure your code. This option is generally least desirable, but for complex cases it may be the best solution.

2 Likes

the borrow checker can not see through function calls, function inlining is an optimization pass that happens after the type checker. if you manually "inline" the contents of the function to the call site, the borrow checker does indeed see it's disjoint fields, and the code is accepted:

    for (_, value) in test.vec.iter_mut().enumerate() {
        *value = true;
        println!("{:?}", test.other);
    }

if you don't mind an third party crate and some macros, using the crate partial-borrow, your code can be rewritten as:

use partial_borrow::prelude::*;

#[derive(Debug, PartialBorrow)]
struct Test {
	vec: Vec<bool>,
	other: bool,
}

fn main() {
	let mut test = Test {
		vec: vec![true, false],
		other: true,
	};
	let (partial_borrow_field_other, partial_borrow_rest) = partial_borrow::SplitOff::split_off_mut(&mut test);
	for (_, value) in partial_borrow_rest.vec.iter_mut().enumerate() {
		*value = true;
		partial_borrow_field_other.print_other();
	}
}

in this case, because print_other() is unambiguous, you don't need type annotation for the SplitOff::split_off() function, that might not be the case for more complicated scenarios.

due to the limitation of the partial! macro, the method print_other() cannot be implemented inherently on the partially borrowed proxy type like this:

// cannot do this:
impl partial!(Test const other, !*) {
	fn print_other(&self) {
		println!("{:?}", *self.other);
	}
}

instead you must implement it as an extension trait like this:

trait PrintOther {
	fn print_other(&self);
}
impl PrintOther for partial!(Test const other, !*) {
	fn print_other(&self) {
		println!("{:?}", *self.other);
	}
}

but there's crates like easy-ext to reduce the boilerplates for extension traits.

1 Like

Thanks, your answers help a lot understanding my different options.

In this simple example, I have the feeling the best and aesthetic way is just to forget the iterator and just just use a for loop with the index:

    for index in 0..test.vec.len() {
        let value = test.vec.get_mut(index).unwrap();
        *value = true;
        test.print_other();
    }

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.