Pre-RFC: Iterator::extend_into


#1

I’ve got an idea about improving Iterator. Since it came into my head I started noticing more and more places, where I wished I could use this. At first I didn’t find any trace of anybody proposing it, but somebody pointed me to this Rust issue, which is similar, but has different perspective. This was posted originally on Rust users forum. What do you think about this idea?

Summary

Add Iterator::extend_into method, which takes a collection and extend it with iterator’s self.

Motivation

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(...);
        .extend_into(vec);
}

extend_into could return the extended collection:

fn my_fn(&self, vec: &mut Vec) -> usize {
    self.items.iter()
        .filter(...)
        .enumerate()
        .map(...);
        .extend_into(vec)
        .len()
}

thanks to that the function could conveniently accept collections by both reference or value:

fn my_fn(&self, mut vec: Vec) -> Vec {
    self.items.iter()
        .filter(...)
        .enumerate()
        .map(...);
        .extend_into(vec)
}

Implementation

No rocket science here.

pub trait Iterator {
    type Item;

    ...

    fn extend_into<B, E>(self, extended: B) -> B
    where
        B: BorrowMut<E>,
        E: Extend<Self::Item> {
        extended.borrow_mut().extend(self);
        extended
     }
}

The Borrow allows passing collections either by value or by reference, which makes it much more elastic.


#2

I like the idea! For your my_fn examples, I think they should pass &mut vec to extend_into though, since my_fn takes vec by reference, no? Making separate examples for passing by reference and value is probably the best way to express this :slight_smile:


#3

Thank you! I’ve added an example of passing and returning by value. I’m not sure, if I understood your comment correctly, but in the previous examples my_fns take vec: &mut Vec, so they don’t need to pass &mut vec into exten_into, because that would make them them actually pass &mut &mut Vec?


#4

Bikeshedding: I’d rather call it collect_into.

To me the phrase source_iterator collect_into destination_vector sounds more intuitive than source_iterator extend_into destination_vector. The latter would make me believe the iterator gets extended somehow (at least at first sight).


#5

Oh right; that’s my Rust newbness showing :smiley:


#6

I definitely like being able to pass both Vec::with_capacity(10) and &mut some_vec :+1:

Some musing on the proposed details:

  • Feels kinda odd that it’s BorrowMut, but I guess that’s because there’s a T-for-T for that, unlike AsMut (which only has it for Vec<T>? :confused: I never understand which to use between those two…)
  • How does using BorrowMut here compare to having a blanket impl like impl<'a, C: Extend<T>, T> Extend<T> for &'a mut C?

#7

Isn’t it too late to consider such a thing? I thought blanket impls on references were a breaking change.


#8

Why BorrowMut feels odd? It’s exactly the use case, which it was made for, to make refs and value act the same. Like you said, BorrowMut is implemented for every T and &mut T, while AsMut for every &mut T but only some Ts.


#9

Note that Borrow and BorrowMut take this quite far: They demand that Hash, Ord, and Eq impls must agree. That’s because they are actually made for collection elements (e.g., to be able to search a map of String keys with a &str key), while AsRef/AsMut is the more general one. In that sense, it is quite strange to use BorrowMut for something that has nothing to do with Ord, Eq, or Hash.


#10

Hmm, I looked up the BorrowMut docs and found no Hash, Ord or Eq constraints, so I dont’ think they will ever get in user’s way. The fact, that these methods operate on &self, indeed makes result of T.borrow_mut() impl them if T does, but I can’t understand how does it matter?


#11

It’s not that the traits require Ord/Eq/Hash to be implemented too, it’s that they require:

If you are implementing Borrow and both Self and Borrowed implement Hash, Eq, and/or Ord, they must produce the same result.

(Source: https://doc.rust-lang.org/std/borrow/trait.Borrow.html)


#12

I see, so you’re saying, that BorrowMut might be too strict for this use case? An alternative would be to define own trait, almost the same, but without these soft constraints. But is it worth it? BorrowMut is already in standard library and widely used, so interface using it might be in practice more elastic.


#13

AsMut is the correct trait for this behavior, but there is no AsMut<T> for T for coherence reasons.


#14

I disagree. Documentation states that AsRef is to be used when wishing to convert to a reference of another type. Borrow is more related to the notion of taking the reference. This use case is about obtaining reference no matter in what form comes the collection, not about converting reference type.


#15

Because other places that allow both owned or borrowed use AsRef, like https://doc.rust-lang.org/std/fs/struct.File.html#method.open


#16

That is done almost exclusively for specific (and almost always unsized) targets. In my eyes, AsRef is useless for abstracting over arbitrary T and &T.


#17

The Path case is not about owned and not owned, it’s about cheap conversion. Look up, what is AsRef<Path>:

  • Components<'a>
  • std::path::Iter<'a>
  • Path <== that had to explicitly implemented for ease of use
  • OsStr
  • `Cow<'a, OsStr>``
  • OsString
  • str
  • String
  • PathBuf

Also notice, what’s not AsRef<T>:

  • AsRef<BTreeMap> for BTreeMap
  • AsRef<BTreeSet> for BTreeSet
  • AsRef<LinkedList> for LinkedList
  • a lot more

That’s because AsRef is to be used when wishing to convert to a reference of another type. Borrow is more related to the notion of taking the reference.