Mutating a Vec while iterating through it and keeping order

⚓ Rust    📅 2026-06-22    👤 surdeus    👁️ 1      

surdeus

Hello, I found myself needing to mutate a Vec while also iterating through it, and keeping the order of it after I'm finished. I found a solution that is currently working, but I would appreciate any feedback on the code if there is a more idiomatic safe way to approach this.


To showcase the solution I came up with I've abstracted the design away from my project into a simplified example. I have four enum variants that help the control flow of this example:

enum Choice {
    Purify,  // Needs to be changed.
    Changed, // The result of being changed.
    Remove,
    Keep,
}

I then create a Vec of these enums inside the variable x:

let mut x: Vec<Choice> = vec![
    Choice::Purify,
    Choice::Remove,
    Choice::Keep,
    Choice::Keep,
    Choice::Remove,
];

The goal is to iterate through x and change Purify into Changed, and remove all Remove variants while maintaining the original order of the vector. In this very simplified example there is only one remove type, but in my project that is determined per-iteration.

As x is defined above the goal would be:

[
    Changed,
    Keep,
    Keep,
]

I can achieve this list by what I call "plucking." I create a while loop to iterate through the index of the vector and remove the first index element, since it is removed from the vector I can then mutate the vector using retain() on it to remove or keep elements. Afterwards I add the plucked element back to the end of the vector by using push(). Note, when removing an element from the vector I subtract it from the while loop's end. This gives it a "treadmill" like effect because all of the elements are removed and re-added in the same order they were in originally.

let mut i = 0;
let mut end = x.len();

while i < end {
    // Pluck the first element always
    // as this moves like a treadmill.
    let mut plucked = x.remove(0);
    if matches!(plucked, Choice::Purify) { plucked = Choice::Changed; }
    
    x.retain(|choice| {
        // Remove anything that matches our logic.
        if matches!(choice, Choice::Remove) {
            end = end - 1; // Since removed, decrease end.
            false
        } else {
            true
        }
    });
    
    // Add the plucked back to the end.
    x.push(plucked);
    
    // Increase the index for looping.
    i += 1;
}

You can view the results of this here on the Rust Playground.


Is there a more idiomatic safe way to handle this? Or in general a way to improve this code? Thank you! :slight_smile:

3 posts - 2 participants

Read full topic

🏷️ Rust_feed