Why can't I modify a Vec while iterating over it?
Short answer: Iterating over a Vec borrows it for the whole loop, and the methods that
change it — push, remove, clear — need a mutable borrow of the same Vec. The compiler
refuses, with E0502. What works instead: filter with retain, take the elements out with
drain, or build a new Vec and swap it in.
From a fresh project — cargo new vec-mod-demo, then replace src/main.rs with each block
below and run it with cargo run. The project name shows up only in cargo’s own output;
nothing else here depends on it.
The error you hit
Section titled “The error you hit”The usual shape is a loop that deletes or moves elements as it goes:
fn main() { let mut numbers = vec![1, 2, 3, 4]; for (i, n) in numbers.iter().enumerate() { if n % 2 == 0 { numbers.remove(i); } } println!("{numbers:?}");}error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable --> src/main.rs:5:13 |3 | for (i, n) in numbers.iter().enumerate() { | - -------------------------- | | | | | immutable borrow occurs here | _____| immutable borrow later used here | |4 | | if n % 2 == 0 {5 | | numbers.remove(i); | | ^^^^^^^^^^^^^^^^^ mutable borrow occurs here6 | | }7 | | } | |_____- this for loop borrows `numbers` immutably, preventing mutation within its body | = help: consider using an index-based loop instead, or collecting modifications into a separate collection
For more information about this error, try `rustc --explain E0502`.iter() holds a shared borrow of numbers for the whole loop, and remove wants the Vec
to itself. The two overlap, so the build fails before the program ever runs. The help line
suggests an index-based loop instead; with remove that compiles, but it skips elements and
then runs off the end — removal index (is 2) should be < len (is 2), exit code 101. The
fixes worth taking are the ones below, and the full anatomy of E0502 is on
the mutable-borrow page.
Keep some, drop the rest: retain
Section titled “Keep some, drop the rest: retain”fn main() { let mut numbers = vec![1, 2, 3, 4, 5]; numbers.retain(|n| n % 2 == 1); println!("{numbers:?}");}[1, 3, 5]retain keeps the elements the closure returns true for and drops the rest, in one pass.
The closure gets a reference to each element, the survivors keep their order, and the Vec
is still there afterwards. Whenever the job is “delete the elements that match”, this is the
call — the loop disappears entirely.
Take the elements out: drain
Section titled “Take the elements out: drain”fn main() { let mut names = vec![ "Ada".to_string(), "Grace".to_string(), "Linus".to_string(), ]; let taken: Vec<String> = names.drain(..).collect(); println!("taken: {taken:?}"); println!("names: {names:?}"); println!("len: {}, capacity: {}", names.len(), names.capacity());}taken: ["Ada", "Grace", "Linus"]names: []len: 0, capacity: 3drain(..) removes a range and hands the elements over as owned values — the Strings above
move out, they are not copied — so the loop body finally owns what it iterates over. The
Vec itself survives, empty: it still reports capacity 3, so refilling it to the same size
costs nothing. Use it when the elements are leaving for good but the binding has to stay.
Build a new Vec: collect first
Section titled “Build a new Vec: collect first”fn main() { let numbers = vec![1, 2, 3]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect(); println!("{doubled:?}"); println!("{numbers:?}");}[2, 4, 6][1, 2, 3]When each new value depends on the old ones — an average of neighbouring elements, say — the
old Vec is the input and a new one is the output. iter().map(..).collect() builds the
answer without touching the original; note that nothing here is mut, because nothing is
being modified. If the original isn’t needed afterwards,
into_iter() in place of iter() consumes it and moves the elements out instead of borrowing
them: over the list above, into_iter().filter(|n| n % 2 == 1).collect() gives [1, 3].
Changing values in place
Section titled “Changing values in place”One more case, since “modify” covers it: when the loop only writes through the element it is
holding, iter_mut() hands out one &mut per iteration and there is nothing to conflict
with. That loop is on the mutable-borrow page too.
Related
Section titled “Related”- Working with collections
- How to get an element from a Vec without panicking —
the other Vec read, and the
Optionit returns - Why does Rust allow many immutable borrows but only one mutable? — E0502, in full
- How to loop over a Vec without moving it —
&,&mutanditer_mut()on the loop itself
Next in this chapter: what is the difference between a Vec and a slice?