Jiaming Lu
Jiaming Lu

Reputation: 885

How can I remove members of a collection type in place?

I want to iterate over a collection type in a struct and remove some values, but Rust prevents me from destroying the collection:

fn some_method(&mut self) {
    self.collection = self
        .collection
        .into_iter()
        .filter(/* ... */
        .collect();
}

I could clone all of the values to build another collection, but that's not efficient. What's the idiomatic way of removing a value from the collection in place in Rust?

Upvotes: 1

Views: 788

Answers (2)

loganfsmyth
loganfsmyth

Reputation: 161517

Since collection is a BTreeMap and does not have .drain() or .retain, and you're fine with emptying the tree during processing, the thing to do would be to move the tree out of self.collection, manipulate it how you want, and then put it back.

As you've seen, Rust doesn't allow that with simple assignment, because if there were a panic during the time when your snippet runs, self.collection would be left in an inconsistent state. Instead, you need to do that explicitly using std::mem::replace, which allows you to take ownership of the content in a mutable reference by providing a replacement for it, so the reference continues to point at valid data.

fn some_method(&mut self) {
  self.collection = std::mem::replace(&mut self.collection, BTreeMap::new())
    .into_iter()
    .filter(|_| true)
    .collect();
}

Upvotes: 1

Kajal Sinha
Kajal Sinha

Reputation: 1575

A full example (also with into_iter):

#[derive(Debug)]
struct Scores {
    collection: Vec<i32>,
}

impl Scores {
    fn new() -> Scores {
        return Scores {
            collection: Vec::new(),
        };
    }

    fn filter_in_above_50(&mut self) {
        self.collection = self
            .collection
            .drain(..)
            .filter(|score| score > &50)
            .collect();
    }

    fn filter_in_above_50_using_into_iter(&mut self) {
        let coll: &mut Vec<i32> = self.collection.as_mut();
        let coll: Vec<i32> = coll
            .into_iter()
            .filter(|score| score > &&mut 50i32)
            .map(|&mut x| x)
            .collect();
        self.collection = coll;
    }
}

And the tests:

#[test]
fn score_test() {
    let mut s = Scores::new();
    s.collection.push(199);
    s.collection.push(11);
    s.filter_in_above_50();
    assert_eq!(s.collection, vec![199]);
}

#[test]
fn score_test_using_into_iter() {
    let mut s = Scores::new();
    s.collection.push(199);
    s.collection.push(11);
    s.filter_in_above_50_using_into_iter();
    assert_eq!(s.collection, vec![199]);
}

Upvotes: 1

Related Questions