Mutable and immutable in loop

Hi, I'm sure this is noob stuff but...here it goes

    let mut numbers: Vec<u32> = (1..100).collect();
    let mut last_index = 0;
    
    while !numbers.is_empty() {
        let next = numbers.iter().enumerate().filter(|(_, number)| last_index % **number == 0).next();
        if let Some((index, removed)) = next {
            numbers.remove(index);
            println!("removed {} @ {}", removed, index);
        }
        last_index += 3;
    }

which, naturally, results in the error

cannot borrow `numbers` as mutable because it is also borrowed as immutable

mutable borrow occurs hererustc(E0502)
main.rs(83, 20): immutable borrow occurs here
main.rs(85, 13): mutable borrow occurs here
main.rs(86, 41): immutable borrow later used here

How would one go about solving these kinds of issues, i.e., an item is selected from the vector based on some logic that iterates it and then it is removed?

Thank you for you patience.

Vec::retain() is useful for a lot of situations like this, but it doesn't provide access to the index of the item. I'm having trouble understanding what this code is actually trying to do, so it's hard to propose an alternative.


Edit: In this particular case, you don't need to get a reference to the item from the iterator because Vec::remove will return it:

fn main() {
    let mut numbers: Vec<u32> = (1..100).collect();
    let mut last_index = 0;
    
    while !numbers.is_empty() {
        let next = numbers.iter()
            .enumerate()
            .find(|&(_, &number)| last_index % number == 0)
            .map(|(i,_)| i);
        if let Some(index) = next {
            let removed = numbers.remove(index);
            println!("removed {} @ {}", removed, index);
        }
        last_index += 3;
    }
}
1 Like

The let next line can be simplified using .position:

let next = numbers.iter().position(|&number| last_index % number == 0);
3 Likes

The code provided is a simplified version of what I need and the smallest I could get that would cause the issue. Your solution doesn't cause the issue and I don't understand why and, thus, (still) help. Could you, please, explain the rational that makes your code work even when both mutable and immutable borrows live inside a loop?

Your original code, with some type annotations:

        let next: Option<(usize, &u32)> = numbers.iter()
            .enumerate()
            .filter(|(_, number)| last_index % **number == 0)
            .next();

Note that next includes a reference to the u32 which is still in your Vec. This is what is causing the conflict: You try to remove something from your Vec while still holding on to a reference that points into that same Vec. You then try to use that reference, in the println! after the remove.

If your code is in fact dealing with u32 or any other Copy type, this would be enough to fix it:

        // Type is now `Option<(usize, u32)>`
        let next = numbers.iter()
            .enumerate()
            .filter(|(_, number)| last_index % **number == 0)
            .map(|(i, n)| (i, *n)) // <-- new
            .next();

Here, we make a copy of the value instead of taking a reference to the value. It's one way of getting rid of the reference to satisfy the borrow checker.

Hopefully this explains the problem for you. It's not that there are both mutable and immutable borrows in the loop at all, but rather that there are mutable and immutable borrows active at the same time. In fact, another way to make your example compile is to remove the println!, because then the reference can be discarded before the remove -- you don't use it anymore. (I've assumed you need the println! or at least the value, though.)

However @2e71828's solution is more general (i.e. better) in that it finds the index, discards the reference entirely, and then uses the result of remove to capture the removed value. It doesn't require that the values in the Vec be Copy. But it avoids the error for similar reasons -- it gets rid of the reference that was causing a conflict.

(They also ergonomically replaced filter() + next() with find().)

1 Like

And here's a couple programs that produce the same output with no Vec. I believe they will be the same for any value of limit, but haven't proved it rigorously :slight_smile:. See also.

1 Like

Thank you for the eloquent clarity. I stand elucidated.

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.