Help me understanding this borrow checker error

Hi,

Could you help me to understand why borrow checker is complaining about immutable/mutable issue on this code?

I guess that it's because the XIterator is storing (in field iterator) an iterator over vector v inside of instance x, so even it seems that XIterator is borrowing just an iterator, the iterator itself is borrowing the x instance.
This is my guess, but I am not 100% sure to have get it right. :flushed:

Thank you.

The error message is pretty descriptive:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  --> src/main.rs:44:9
   |
41 |     let v: Vec<&i32> = x.into_iter().collect();
   |                        - immutable borrow occurs here
42 |     
43 |     for _ in v.iter() {
   |              -------- immutable borrow later used here
44 |         x.set();
   |         ^^^^^^^ mutable borrow occurs here

The issue is that v is borrowing every element of x, so you will be unable to mutate x until v is dropped.

If you really want to mutate the vector while iterating, you can switch to iter_mut. But you won't be able to mutate through the x binding while a borrow (mutable or immutable) exists.

For example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=162f020881c3c577f595ea3786d6f437

Yeah, I know it was quite a dumb question, but I was not 100% sure, since the error is referring to x and v, but anyway I can see the reason.
v is a vector of reference of elements inside of x, so the borrow checker is doing its job to protect me on doing wrong things :wink:

Here is a very thorough explanation of how the borrow checker sees it:

Whenever you have any function that links return data with input data using a lifetime, the input reference(s) must be valid as long as the output reference is held.

For example

// this has a lifetime
fn explicit<'a> (borrowed: &'a i32) -> SomeStruct<'a>;

// this has a lifetime too, only it's implicit
fn implicit(borrowed: &i32) -> &i32;
// this would be equivalent and more explicit
// fn implicit<'a> (borrowed: &'a i32) -> &'a i32;

In this case, .into_iter() is essentially a function with this signature

fn into_iter<'a> (self: &'a X) -> XIterator<'a>;

Which means that as long as that the borrow inside that XIterator is held, the reference to that X must remain valid.

After calling .into_iter(), you then call .collect() on the XIterator. In this case, it is essentially a function with the signature:

fn collect<'a, 'b> (self: &'b mut XIterator<'a>) -> Vec<&'a i32>;

As long as any of those &'a i32s are held, the borrow inside XIterator<'a> must be valid, which means the borrow of &'a X must be valid.

Then you call .iter() on your Vec<&'a i32>, which, without going into yet another function signature, ensures that you are still holding that vector (because you need to continue iterating over it) for the entire duration of the loop.

Then you call .set() on the original X, which thanks to it's function signature, requires a mutable reference. So you try to mutably borrow x, but thanks to the aliasing rules, you can't mutably borrow x while it is also borrowed immutably.

It's immutable borrow needs to persist because the lifetime inside the XIterator is still held. The lifetime inside the XIterator is still held because the lifetime inside the references inside the Vec<&i32> is still held. That lifetime is still held because the iterator obtained by calling .iter() is still held. That iterator is still held because the for loop is still in progress.

The borrow checker can reason through all of this without even referencing the code within any of the methods I described. All it needs are the function signatures because they tell you the dependencies between the lifetimes.

Does that clarify the error?

2 Likes

Wow! Thank you very much!
Now it is not just a guess or feeling, but it is like thinking as the borrow checker :smile:

1 Like