Reputation: 817
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:
&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); }
}
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
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
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.
Upvotes: 2