Check out a free preview of the full Rust for TypeScript Developers course

The "Understanding Collect" Lesson is part of the full, Rust for TypeScript Developers course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen demonstrates the functionality of the collect method by implementing a custom version of it. Values from the original vector are pushed into a new vector as they are iterated. The new vector becomes available after the iterateration is complete.


Transcript from the "Understanding Collect" Lesson

>> All right, so here we go hopefully your code looks something like this. Yeah, everyone followed along, did everyone follow along successfully? Yeah, can I see some yeses, okay, I see some heads going up and down. Okay, so what is collect, this is probably a lot different than what you're used to.

Of course we need to convert back into a structure. So, we're gonna do this manually right now, I'm gonna show you how to manually do a collect, so what it does. So let's go like this, the first thing I'm gonna do is I'm gonna drop this collect. All right, and then I'm just gonna do this, all right.

Then I'm gonna also drop this type definition, because it's gonna be wrong. Right away I've ran into some sort of weird error, and if you're following along you probably ran into the same error as well. This is the borrow checker, rearing its ugly head coming up. And this will make a lot of sense, we're actually gonna go over this exact problem here in a little bit, but this is fantastic that we ran into it.

So I want to take foo, and I now want to convert it from an iterator back into a new vector. So let's go like this, new vector equals vec bang and I'll do that. Now of course we need to make things mutable, so I'm gonna go mute on this and I'm gonna go up to our iterator and also type in mute.

Because to be able to call next on an iterator, you need to be able to mutate its underlying contents, how I had that index in the drawing. Something needs to be able to update itself, so I'm saying hey, you are allowed to be updated. So now I'm gonna go like this, I'm gonna go while let some, so here's pattern matching.

We're encountering an undefined potentially, so don't worry we'll go over this more. But I'm gonna go like this, x equals, remember next calls the next value out of an iterator. At some point the iterator will stop producing values, hence the reason why it could be an undefined value.

So if I hover over the documentation, it says it advances the iterator and returns the next value, that makes sense, right? So that's why it's gonna be an option, of item as opposed to just the item itself. All right, so now that we've done that, we have a wild statement that will run until an undefined is returned, it's just gonna keep on running cuz we're pattern matching it out.

It won't be able to pattern match and none, so therefore, it will stop being inside that while loop. So here I can go new vector.push x, I'm now pushing x into my new vector. And then down at the print statement, I can go new vector. We still have this one problem and this of course is a Baro checker problem, I'm gonna give you a brief little description of what's happening here.

But I want someone to just try to guess, with the best of your ability based on this error message, what went wrong here? It's good just to exercise these muscles. Well, let me tell you this. This iterator refers to the vector, right. It's not the vector itself, it refers to the vector.

Which means how long does this value live, who owns this value, nobody does. It was created temporarily, we create a reference to it. And then it went away, and so now we just have a reference to nothing. So in the Rust land, you'd have to go like this, let data = this and data.

So now we have something in which we said it's gonna live long enough, so that we can now refer to it for the rest of our function. This is a very kind of rusty concept but I wanted to get it in your head a little early so that you can kind of see some of these things.

Because it makes you think of your program a little bit differently, you start thinking about how long do my variables live, because that's very important. Because if I'm referring to things, and they stop living, well, what happens, that memory could be completely different. You could be referring to something different.

This week we have these things, this idea of C-dangling pointers where you're like, off referring to who knows what and stuff explodes and it's very terrible. This is one of those reasons why it could be terrible, all right. So I'm gonna move on, does everyone kinda see how collect works.

So in the sense that you create a vector, we go through the iterator one at a time, and we push the map values into a new vector. And then that is what collects does, it's just doing this for us. It's just not magic anymore, it's actually pretty simple.

You could do this in TypeScript pretty easily, all right. I Yes?
>> So data now lives on while main is alive.
>> Yes, data now lives for the duration of main, if you will. Hopefully that kinda demystifies some level of what collect does, so instead of calling collect, we just manually took out one value at a time out of our iterator.

Put it into a collection and then at the end we've just performed a collect. The thing is is most things in programming aren't magical, if you take a moment to think about it, you could probably create most of it pretty easily. It's just a lot of these convenient functions, all right, so you wanna see something pretty dang cool about collect, that's a little unusual?

Well, have you ever been into a situation, where you wanna join a bunch of strings? Well, in JavaScript you just do.join, right? Well, we can do the same thing here with collect. I called collect, but I called collect, I'm not in prison. I'm calling the function collect and notice that the string is my type, that was smart enough.

To call the underlying function collect, on a string to gather all of these into a string as opposed to a vector. By simply stating its type collect behaved differently. Again, I could do with a hash set, have you ever had to convert an array into a hash set?

We'll look at this, you could have a vector ,turn it into an iterator and collect it. And because the type is hash set, collect is smart enough to then run the hash set code as opposed to the vector code. It's now a hash set, you can do it with the map long as you have a tuple coming out of your map or out of this map.

You can collect it into a hash map. A hash map is the equivalent of a map in TypeScript, they're effectively the same thing. So look at that, I am now having a delicious map by using collect like that's pretty cool, right? Right, yes thank you, okay. I wanna see people get excited, okay?

For me, this is really exciting. By the way this is just an example of destructuring going on right here, pretty fantastic. So what's happening right here for those that don't know, we have a vector of string or string references, right? The string references are referring to strings, that are literally in our program.

So when our program compiles, quote this quote lives somewhere within the memory of the binary, so this is called a static string reference or a static str. But don't worry about it, we just say this, there you go. And then into iterator, so instead of referring to the vector, it actually takes the vector and converts it into an iterator.

So this is how we'd get around that previous borrow checker problem as well, if we could convert our data into an iterator as opposed to referring to it. I know a little confusing, a numerate in JavaScript when you do a dot map your second argument is what index it is.

In rust that doesn't exist because you didn't tell it, so enumerate makes it so that the first argument is index, and the second one is the value. Kind of cool and so I can say, hey I actually do want indexes. So I said, you're an iterator, you're an iterator vector, give me indexes, and I'm gonna change you from index item to item index in the tuple.

Because I want to go from string to number, not from number to string, and then I can call collect. And now we have a HashMap, look at that it's fantastic. There's also some, we're gonna play a little quick game here, we're gonna see how much you guys are understanding, okay.

We're gonna do something cool, so what's this, what is the value of value?
>> 6.
>> Yes, it's a perfect number, by the way isn't that kinda cool? One plus two plus three is also its components and blah, blah, and adds to itself. Anyways, so there you go fantastic.

You can take an iterator and instead of calling collect, you can actually perform this operation, such as sum, it converted the entire vector into a value. What about this one, what is the value of how many times?
>> Three, wait no, one.
>> Yes, it's one, we just called count on it.

So we converted it from an iterator, and just counted how many times it could call next, and then return that. So just like collect, count is almost no different. All right, what will this print? Now, this is a lot of craziness going on here, so you're just gonna have to try and struggle your way through this, but what do you think it's gonna print?

>> 9?
>> 5 and 9?
>> 5, 9?
>> Or skipping 2?
>> 5, 9 was the correct answer. It's gonna skip 1, it's gonna skip 2, it's gonna take while x is greater than 4, which is 5, then 9. Then the next 4 is not greater than 4, therefore it stops, and for each will print out each line.

Pretty cool, right there's a lot of little combinators you can do with your iterator so long as you kind of live in this world where you're making sure you're using iterators. All of a sudden you have a huge expressive set of how to work with your iterators. It's pretty neat though, all right, last time, what about this one.

What are we gonna get here, for those that don't know, x modulo 2, checking for evenness.
>> Either 1 and 3, or 2, or either 1 or 2.
>> It is 1, because remember, we don't go over indices, we're going over the values themselves. So 1 is not even, 2 is even, 3 is not even count.

>> I wasn't sure what the asterisks might have like, selected for things that were false or selected for things that were true, I wasn't sure.
>> So the asterisk itself is this right here, is referring to the contents inside the vector. That means x is not gonna be an integer, it's gonna be a reference to an integer.

And since it's also using filter, filter doesn't consume, so it's actually a double reference to an integer. So by throwing this out here, since copies implemented blah, blah, you now have an integer it's beautiful and now you can compare it to 2. I know it's a super long winded way to say it, don't worry.

It becomes very obvious how and when to do this, the more you use it. And often, this is just syntax, I rarely ever do this it's just for this. This happened so it's a very specific thing I'm just having to do it. All right, so you can also create iterators from other collections, so I can have a HashMap and call for each.

Now notice that this for each has a much better definition than my right key than value it doesn't have to feel great, anyways so there you go so I can just say hey map. Give me a reference iterator, and I can go over every single value. Which means I can also skip, I can take while, I can do skip while, I can do all the different combinators.

And if you're feeling extra spicy you can even write your own, and it'll integrate right into this. You can also do iterators from hash sets. And in fact, you can even create your own iterators if you ever wanted to. So if you have your struct called to do, you could actually do a for loop that would go over each one of them, because you allowed it to be iterable effectively.

You created an iterator for it in some implementation, and we're gonna actually do this so that's like the end goal of today. Is to get to the point where we can create our own iterators, all right yes Mark.
>> Would the borrow checker error on some of these should we use into inter instead.

>> Well since they're all sing, so remember that earlier Baro checker error we had, since these are all individual you will notice that I don't hold on to any value it won't drop. So the duration of this will last for the duration of all the statements, so therefore we won't have any Baro checker problem.

Baro checker only happens, you could use Intuitor, right? Intuitor would probably be better, we'll give a little bit better explanation of that in a little bit. Intuitorator returns the iterator as the item and it now owns the underlying data. Whereas, Iter doesn't own the underlying data, it only refers to it so you don't consume it.

And that becomes much more meaningful, once you understand value in the Baro checker cuz you don't sometimes want to consume your array every time you create an iterator. You wanna be able to go through it and then still have it at the end. And so, it'll make more sense.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now