-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Summary
Add Iterator::collect_into
method, which takes a collection and extends it with iterator’s content
Motivation
Ergonomics
Sometimes there is a need to create a sophisticated iterator and then put all it's items into a collection. Of cource there is Iterator::collect
, which creates a new collection, but there is no convenient method of collecting into an EXISTING one.
Sure, it's possible, but a bit awkward:
fn my_fn(&self, vec: &mut Vec) {
let iter = self.items.iter()
.filter(...)
.enumerate()
.map(...);
vec.extend(iter);
}
or more compact, but way harder to follow:
fn my_fn(&self, vec: &mut Vec) {
vec.extend(self.items.iter()
.filter(...)
.enumerate()
.map(...)
);
}
but nothing beats THIS:
fn my_fn(&self, vec: &mut Vec) {
self.items.iter()
.filter(...)
.enumerate()
.map(...);
.collect_into(vec);
}
collect_into
could return the extended collection:
fn my_fn(&self, vec: &mut Vec) -> usize {
self.items.iter()
.filter(...)
.enumerate()
.map(...);
.collect_into(vec)
.len()
}
the function could accept collections by both reference or value:
fn my_fn(&self, mut vec: Vec) -> Vec {
self.items.iter()
.filter(...)
.enumerate()
.map(...);
.collect_into(vec)
}
Manually size-hinted collect
This use case was invented by @stuhood in Rust issue 45840.
As originally determined in https://twitter.com/kot_2010/status/927119253324619776), building a pre-size-hinted mutable Vec
as the output of a loop (even for very small n
) can occasionally be more than twice as fast as collecting into a Vec
. As demonstrated here , using extend into a pre-size-hinted Vec
closes most of the gap.
Focusing on improving Iterator::size_hint
implementations so that they suggest better sizes for collect would likely help reduce this gap. But there are diminishing returns to heuristics, and in many cases users should be able to give better explicit hints.
Guide-level explanation
Iterator::collect_into
takes all items from the Iterator
and adds them to an existing collection. It's a counter-part for Extend::extend
, but instead of being invoked on container, it's called on Iterator making it's usage similar to Iterator::collect
.
let mut vec = vec![0, 4];
(3..7)
.filter(|i| i % 2 == 0)
.map(|i| i * 2);
.collect_into(&mut vec);
assert_eq!(vec![0, 4, 8, 12], vec);
}
Iterator::collect_into
takes collection either by mutable reference or value. It also returns the extended collection making chaining elegant.
let vec = (2..4)
.map(|i| i * 2)
.collect_into(vec![0, 2])
.windows(2)
.map(|c| c[0] + c[1])
.collect::<Vec<_>>();
assert_eq!(vec![2, 6, 10], vec);
Reference-level explanation
No rocket science here.
pub trait Iterator {
type Item;
...
fn collect_into<B, E>(self, extended: B) -> B
where
B: BorrowMut<E>,
E: Extend<Self::Item> {
extended.borrow_mut().extend(self);
extended
}
}
The BorrowMut
allows passing collections either by value or by reference, which makes it much more elastic.
Drawbacks
This proposition makes Iterator
interface even broader without extending its functionality significantly, it's just ergonomics.
Rationale and alternatives
This proposition removes a small stdlib
papercut making it more pleasant and expressive. If it's not added, people will just keep using workarounds with Extend::extend
.
Unresolved questions
None, that I'm aware of.