3
submitted 3 months ago* (last edited 3 months ago) by maegul@lemmy.ml to c/learningrustandlemmy@lemmy.ml

Just a quick riff/hack on whether it'd be hard to make a collect() method that "collected" into a Vec without needing any turbofish (see, if you're interested, my prior post on the turbofish.

Some grasp of traits and iteration is required to comfortably get this ... though it might be a fun dive even if you're not

Background on collect

The implementation of collect is:

fn collect<B: FromIterator<Self::Item>>(self) -> B
where
    Self: Sized,
{
    FromIterator::from_iter(self)
}

The generic type B is bound by FromIterator which basically enables a type to be constructed from an Iterator. In other words, collect() returns any type that can be built from an interator. EG, Vec.

The reason the turbofish comes about is that, as I said above, it returns "any type" that can be built from an iterator. So when we run something like:

let z = [1i32, 2, 3].into_iter().collect();

... we have a problem ... rust, or the collect() method has no idea what type we're building/constructing.

More specifically, looking at the code for collect, in the call of FromIterator::form_iter(self), which is calling the method on the trait directly, rust has no way to determine which implementation of the trait to use. The one on Vec or HashMap or String etc??

Thus, the turbofish syntax specifies the generic type B which (somehow through type inference???) then determines which implementation to use.

let z = [1i32, 2, 3].into_iter().collect::<Vec<_>>();

IE: Use the implementation on Vec!

Why not just use Vec?

I figure Vec is used so often as the type for collecting an Iterator that it could be nice to have a convenient method.

The docs even hint at this by suggesting that calling the FromIterator::from_iter() method directly from the desired type (eg Vec) can be more readable (see FromIterator docs).

EG ... using collect:

let d = [1i32, 2, 3];
let x = d.iter().map(|x| x + 100).collect::<Vec<_>>();

Using Vec::from_iter()

let y = Vec::from_iter(d.iter().map(|x| x + 100));

As Vec is always in the prelude (IE, it's always available), using from_iter clearly seems like a nicer option here.

But you lose method chaining! So ... how about a method on Iterator, like collect but for Vec specifically? How would you make that and is it hard??

Making collect_vec()

It's not hard actually

  • Define a trait, CollectVec that defines a method collect_vec which returns Vec<Self::Item>
  • Make this a "sub-trait" of Iterator (or, make Iterator the "supertrait") so that the Iterator::collect() method is always available
  • Implement CollectVec for all types that implement Iterator by just calling self.collect() ... the type inference will take care of the rest, because it's clear that a Vec will be used.
trait CollectVec: Iterator {
    fn collect_vec(self) -> Vec<Self::Item>;
}

impl<I: Iterator> CollectVec for I {
    fn collect_vec(self) -> Vec<Self::Item> {
        self.collect()
    }
}

With this you can then do the following:

let d = [1i32, 2, 3];
let d2 = d.iter().map(|x| x + 1).collect_vec();

Don't know about you, but implementing such methods for the common collection types would suit me just fine ... that turbofish is a pain to write ... and AFAICT this isn't inconsistent with rust's style/design. And it's super easy to implement ... the type system handles this issue very well.

top 2 comments
sorted by: hot top controversial new old
[-] Jayjader@jlai.lu 2 points 3 months ago

The idea & execution are great, I just don't know that I would ever do this for collecting into Vecs myself.

When I'm tired of writing turbofish, I usually just annotate the type for the binding of the "result":

let d = [1i32, 2, 3];
let y: Vec<_> = d.iter().map(|x| x + 100).collect();

So often have I collected into a vector then later realized that I really wanted a map or set instead, that I prefer keeping the code "flat" and duplicated (i.e. we don't "go into" a specific function) so that I can just swap out the Vec for a HashMap or BTreeSet when & where the need arises.

[-] maegul@lemmy.ml 2 points 3 months ago

So often have I collected into a vector then later realized that I really wanted a map or set instead, that I prefer keeping the code “flat” and duplicated (i.e. we don’t “go into” a specific function) so that I can just swap out ...

Yea good point. And yea, annotating the binding rather than using the turbofish also seems more natural to me too.

In the end though, my motivation here was to see if I could, not whether I should! 😜

Though to be fair, I can see myself adding collect_vec() to a codebase if I know I will be collecting into a bunch of vecs. Just because it's my little monster and I'm biased! And adding other methods for the other common collections probably wouldn't be too hard??!!

this post was submitted on 26 Jul 2024
3 points (100.0% liked)

Learning Rust and Lemmy

231 readers
1 users here now

Welcome

A collaborative space for people to work together on learning Rust, learning about the Lemmy code base, discussing whatever confusions or difficulties we're having in these endeavours, and solving problems, including, hopefully, some contributions back to the Lemmy code base.

Rules TL;DR: Be nice, constructive, and focus on learning and working together on understanding Rust and Lemmy.


Running Projects


Policies and Purposes

  1. This is a place to learn and work together.
  2. Questions and curiosity is welcome and encouraged.
  3. This isn't a technical support community. Those with technical knowledge and experienced aren't obliged to help, though such is very welcome. This is closer to a library of study groups than stackoverflow. Though, forming a repository of useful information would be a good side effect.
  4. This isn't an issue tracker for Lemmy (or Rust) or a place for suggestions. Instead, it's where the nature of an issue, what possible solutions might exist and how they could be or were implemented can be discussed, or, where the means by which a particular suggestion could be implemented is discussed.

See also:

Rules

  1. Lemmy.ml rule 2 applies strongly: "Be respectful, even when disagreeing. Everyone should feel welcome" (see Dessalines's post). This is a constructive space.
  2. Don't demean, intimidate or do anything that isn't constructive and encouraging to anyone trying to learn or understand. People should feel free to ask questions, be curious, and fill their gaps knowledge and understanding.
  3. Posts and comments should be (more or less) within scope (on which see Policies and Purposes above).
  4. See the Lemmy Code of Conduct
  5. Where applicable, rules should be interpreted in light of the Policies and Purposes.

Relevant links and Related Communities


Thumbnail and banner generated by ChatGPT.

founded 9 months ago
MODERATORS