Long Thai
Long Thai

Reputation: 817

Filter a vector without losing its ownership

Given a struct as follows:

#[derive(Debug)]
struct Item {
    id: u32
}

impl Item {
    fn new(id: u32) -> Item {
        Item { id }
    }
}

I'm looking for a way to perform filter on a vector of that struct without taking its ownership. The following code won't work because the ownership has been moved:

fn main() {
    let items: Vec<Item> = vec![Item::new(1), Item::new(2), Item::new(3)];
    let odd_items: Vec<Item> = items.into_iter()
            .filter(| item | item.id % 2 == 1)
            .collect();
    for i in odd_items.iter() { println!("{:?}", i); }

    for i in items.iter() { println!("{:?}", i); }
}

Currently, I have 2 solutions:

  1. Having a vector of &Item instead of Item, however, I find it a bit awkward to start from Vec<Item> but end up with Vec<&Item>:
fn main() {
    let items: Vec<Item> = vec![Item::new(1), Item::new(2), Item::new(3)];
    let odd_items: Vec<&Item> = items.iter()
            .filter(| item | item.id % 2 == 1)
            .collect();
    for i in odd_items.iter() { println!("{:?}", i); }

    for i in items.iter() {  println!("{:?}", i); }
}
  1. Clone an initial vector. I prefer this one but it results in unnecessary cloning, I'd prefer cloning only the items after filitering:
fn main() {
    let items: Vec<Item> = vec![Item::new(1), Item::new(2), Item::new(3)];
    let odd_items: Vec<Item> = items.clone()
            .into_iter()
            .filter(| item | item.id % 2 == 1)
            .collect();
    for i in odd_items.iter() { println!("{:?}", i); }

    for i in items.iter() { println!("{:?}", i); }
}

I wonder if there is any better way to filter a vector without losing ownership.

Upvotes: 1

Views: 1989

Answers (2)

user4815162342
user4815162342

Reputation: 154921

Clone an initial vector. I prefer this one but it results in unnecessary cloning, I'd prefer cloning only the items after filtering:

If you want to clone after filtering, you can certainly do so, and it will have the side effect of converting &Item to Item (which is what clone() does by definition):

let items: Vec<Item> = vec![Item::new(1), Item::new(2), Item::new(3)];
let odd_items: Vec<Item> = items
    .iter()
    .filter(|item| item.id % 2 == 1)
    .cloned()     // the same as .map(Item::clone)
    .collect();

BTW if you can get away with Vec<&Item>, that solution might be more efficient because it uses less space (if actual items are larger than a pointer, that is), and it creates odd_items as a "view" into items. If that's not what you want, then go for the cloning, you'll just need to add #[derive(Clone)] to Item.

Also note that, despite its bad rap, clone() doesn't have to be expensive - "cloning" a struct that just contains a u32 is no more expensive than a u32 assignment. It's only when the type is large and/or contains heap-allocated data that cloning becomes a thing to shy away from.

Upvotes: 7

SirDarius
SirDarius

Reputation: 42889

In your specific case, you don't actually need to call collect as the next for loop can use an iterator directly:

fn main() {
    let items: Vec<Item> = vec![Item::new(1), Item::new(2), Item::new(3)];
    let odd_items = items.iter().filter(|item| item.id % 2 == 1);

    for i in odd_items {
        println!("{:?}", i);
    }

    for i in items.iter() {
        println!("{:?}", i);
    }
}

With this technique, odd_items is a binding to an iterator of &Item but type inference hides it (actual type is more complex, as chaining iterators usually results in things like Filter<Map<Iter<...>> etc).

The main advantage of doing so is that no Vec is allocated in addition to items, and the latter retains ownership.

Playground link

Upvotes: 2

Related Questions