EN VI

Why does Rust is borrow checker complain when using an iterator returned from a method, but not when using a Vec is iterator directly?

2024-03-16 01:30:06
Why does Rust is borrow checker complain when using an iterator returned from a method, but not when using a Vec is iterator directly?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ba9255e504174afa0f31f20467caf0a9

Consider the following Rust program

struct Foo {
    bar: Vec<i32>,
    baz: i32
}

impl Foo {

    fn get_an_iter(&self) -> impl Iterator<Item=&i32> {
        self.bar.iter()    
    }
    
    fn do_mut_stuff_works(&mut self) {
        for num in self.bar.iter() {
            self.baz += num;
        }
    }
    
    fn do_mut_stuff_does_not_work(&mut self) {
        for num in self.get_an_iter() {
            self.baz += num;
        }
    }
}

In do_mut_stuff_works I am iterating over the vector bar and change the field baz. To do this, the method needs to take &mut self.

Now if I move the creation of the iterator from inline to a method get_an_iter, and try to iterate over that in do_mut_stuff_does_not_work, the borrow checker complains.

How is it that, when using get_an_iter, the immutable borrow stays alive for the whole loop such that my mutable borrow at self.baz += num fails, but when I just create the iterator inside the method, with for num in self.bar.iter(), the borrow checker is happy?

The full compiler output is:

Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `self.baz` as mutable because it is also borrowed as immutable
  --> src/lib.rs:20:13
   |
19 |         for num in self.get_an_iter() {
   |                    ------------------
   |                    |
   |                    immutable borrow occurs here
   |                    immutable borrow later used here
20 |             self.baz += num;
   |             ^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` (lib) due to 1 previous error

Solution:

Rust can split borrows among fields. In do_mut_stuff_works, you start with a mutable borrow of self. When you take self.bar.iter(), that borrow is split: self.bar is immutably borrowed for the duration of the for loop. During this time, the mutable borrow of self as a whole is invalid, but the mutable borrow of self.baz remains. Thus you can increment self.baz in the body.

In do_mut_stuff_does_not_work, you make an immutable borrow to the whole of self and give it to get_an_iter, so that borrow of self lasts for the whole for loop. Rust does not look into the definitions of functions to split borrows; that would be leaking implementation details about the function that aren't in the signature. So even though get_an_iter only needs an immutable borrow of self.bar, do_mut_stuff_does_not_work only knows that it has lent the entirety of self, immutably, to get_an_iter. Thus there is no mutable borrow of self.baz in the body of the loop that would justify incrementing it.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login