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

The "Coding an Iterator" 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 introduces iterators and creates programs in TypeScript and Rust that define a list of numbers, map through them, and increment their value by one. The result is printed to the console.

Preview
Close

Transcript from the "Coding an Iterator" Lesson

[00:00:00]
>> So we're gonna start coding now. This talking business, it's boring. We've been going at it for an hour. I personally think it's easier to learn with your hands than with your eyes. And so let's start learning with those hands. To begin with, there's two things you need to understand with Rust.

[00:00:14]
Two kind of fundamental things, Iterators and Enums. I feel like this is a good place to start. A lot of people start at the borrow checker, but then you have to have all these words you've never heard before tossed at you. And they just feel confusing, because you're probably thinking, well, I use iterators all the time in TypeScript.

[00:00:29]
What do you mean? And enums are horrible in TypeScript. Don't you know? Well, you're right if you're in TypeScript land. You're wrong if you're in Rust land, it's different. It's much, much different. And you don't use that many iterators in TypeScript. Often, you iterate you don't use iterators.

[00:00:48]
There's a little bit of a difference there. All right, so let's start with iterators. I think they probably have the best analogy to TypeScript and I think we can all understand this. So first let me give you just a quick little brief introduction just so you can kind of see what happens.

[00:01:04]
So often if you have something like a vector, remember that's just a list, and it's 1, 2, 3. An iterator is a separate data structure that can walk through that data structure. So it doesn't really matter what the collection is, if there is an order, an iterator can walk through it.

[00:01:22]
So you can imagine a graph if you're familiar with their data structures and algorithms. A graph, a breadth-first search could also be an iterator that just walks through it. So every time you call next, you get the next step in the graph. So an iterator here, you could imagine, would have some sort of pointer back to this thing, and it would probably have something like an index.

[00:01:41]
Like, where am I at at this point? And then you, having a reference to this iterator, can call something like .next. And this will just give me the next value out. That's really what an iterator is. I think in JavaScript if I'm not mistaken, I could be wrong.

[00:01:55]
There is like a hasValue or there's a done somewhere. It has to tell you when it's done. It's a little bit different in Rust, it goes a little bit differently, they just have next and you know when it's done by the way it is just like an Aspen.

[00:02:09]
You know what it is by the way it is. Trust me, it'll make sense. It's a good joke, but I realize it's not that funny right now. Makes me feel bad. All right, so I think the easiest transition for iterators is to use .map on an array. So people probably typically think of .map on an array as an iterator.

[00:02:28]
It's not, it iterates and produces a new array, but it's similar. But it's completely different at the same time. So let's start doing something with that. So let's do a quick example in TypeScript. I want you guys right now to write a little quick script. If I were you, I would just simply inside the source folder, have like index.ts and then you'll have your index.ts and you'll have your main.rs.

[00:02:51]
Main.rs is analogous to index.ts. Pretty much the same thing. All right, and then let me just erase these things, there we go. And so I want you to create a list with 1, 2, 3, do a map that adds 1 and then print out the list. I hope everyone had the chance to create this beautiful piece of code.

[00:03:16]
Let me just create it for you really, really quickly. I'm gonna jump in here. We're in index.ts. I'm gonna go const. We'll just call it foo equals 1, 2, 3. And then I'll go .map x, x plus one, and then do a console.log(foo). There we go. I assume people probably wrote very similar code at this point.

[00:03:38]
Maybe you put it somewhere else. Let's make sure that I'm on the same list. Look at that. And then to make sure you can run it. Run it once because it'll be good if you can always run everything. So I'll go npx ts-node source/index, there you go, look at that.

[00:03:54]
It takes a second 2, 3, 4. I am great success. Did you guys great success? Did you get 2, 3, 4? If you didn't get 2, 3, 4, well, you got to get 2, 3, 4. All right, so we're gonna do the exact same thing. But we're gonna do it in Rust.

[00:04:09]
Now, you guys probably forgot a lot of the syntax, but remember, this is how closures look. They look just like that. And remember you define a vector by doing this vec bang brackets. I want you to try to write it. I know you haven't written any Rust at all, go to the main function in source/main.rs.

[00:04:31]
And start off define a vector, you're gonna need to call .iter which I put right here that creates it into a nice iterator. And then you're gonna map it. But then you're gonna probably run into a problem, which is, how the heck do you get it into something that I can print?

[00:04:49]
So there'll be a .collect method, just see how far you can get. I'll give you a moment. Fail, it's okay to fail. The point is, is that you're typing it, you're feeling it, you're doing it. Because after a little bit it's gonna become second nature and then all other problems are easier to solve.

[00:05:06]
I know it's always weird doing different syntax feels hard feels disgusting. And then lastly, if you do get all the way through, you can do a println bang and do what is referred to as a debug print or a pretty print. So if you get all the way through, wow.

[00:05:27]
All right, I'm gonna start now. I feel like I've given you enough time. All right, let me just erase my old code there. All right, so here we are. We are in the main function, and I'm gonna go like this. Let foo equals, do I still have Copilot on?

[00:05:43]
There we go. Vec bang 1, 2, 3. All right, so now we gotta convert this into an iterator, .iter. You can kind of see this you have a couple different options here into iterator, iterator mute and iter, we'll just do iter. Then we're gonna map it, .map and we have to do the closure.

[00:06:02]
Remember, closure uses the bars so I can go bar x bar, x + 1, right? That looks pretty much the same thing as TypeScript, right? Like if we jump back here, it's pretty much the same code right there, right? Okay, so now at this point, we have a problem.

[00:06:23]
Because remember this right here, an iterator is not the vector itself, right? It is some sort of additional structure that can iterate over a collection. So right now, what you have is an iterator. So if I highlighted over it, it would be a map that has a generic type iter that has i32.

[00:06:42]
It is a map that's producing an i32. So we now have to convert this into something again. So what I am gonna do is we're gonna do this, .collect. We're gonna collect our iterator into a vector. But if we just do this, it's gonna tell us, hey, type annotations are needed.

[00:07:00]
What do you want to collect it as? I don't know what you want to, because me personally, I know what I want, but it doesn't know what you want. So let's go all the way up to foo. And let's define its type. Just like TypeScript, you go colon, and let's just say this Vec underscore, that just means, hey, infer the type.

[00:07:20]
You know what the type is. I'm just going to say vec. So there you go. So it knows now, collect now knows you want a vector, so it's gonna collect it into a vector for you of the type whatever map does. There you go, that's pretty convenient. And then now we're going to print it.

[00:07:36]
So println bang, remember print's a macro so you gotta do this. And then we're going to do the pretty print. So that of course looks like a guy smoking a pipe. I'm not really sure how to describe this operator, that's kind of what I got to as a, I don't know.

[00:07:52]
If someone has a good name for it, please let me know. And then just pass in foo. There you go. So we've created a list. We converted it into an iterator in some sense. We have mapped over its values and reconverted it back into a list. I know you're thinking, wow, this is so many steps already.

[00:08:14]
You're lying to me when you said Rust was better than TypeScript. Just hold on, we'll get to the cool stuff. Right now we're doing the simple stuff. All right, and if you want to run it, you're gonna wanna go to your terminal and do cargo run. I'm gonna give you one more second to kind of type through this.

[00:08:29]
It's very important to type it.
>> Could you explain what you mean by closure in Rust? I think it might be a little bit different from the JavaScript TypeScript.
>> Yeah, so I'm just using the general term closure. Technically, in the TypeScript world, when you say closure, it actually means, it's actually like a grab bag of things that you've closed over up your scope chain.

[00:08:52]
And so when you do a function every function actually has an associated closure with it. That's why when you have a function that returns a function, any variables that you define bitwixt the definition of the function and the return statement are closed over inside the function that you return.

[00:09:05]
And you can refer to them and add to them. So that's a closure. We use the term closure for this right here, which you can also think of it like a lambda function. So when you do the little parentheses dash arrow the arrow functions you're actually doing a lambda function.

[00:09:19]
In TypeScript, a lambda function is not directly equivalent to a function. In TypeScript, a lambda function has a lexical binding to this. Meaning if you're on a class it automatically binds to this as opposed to binding to the global, I believe is what a function does. And so there's a little bit of implications.

[00:09:34]
They're not one-to-one, so when someone creates top-level lambda functions, it emotionally hurts me because you're using it wrong. But that's because I'm way too specific and you don't need to be that specific.
>> Could you go over why we need collect again? What does that iterator return?
>> So when we look at this thing right here, we create a vector then we covert it into an iterator.

[00:09:57]
So when I called.iter, we're getting an iterator out referring to the vector. This iterator has a pointer to the vector plus probably index 0. This is just me guessing. It might be something different, but I'm just guessing. When we call .map, it's gonna receive the iterator and it's gonna go next and the value that comes out of that iterator, it's going to pass to our closure.

[00:10:20]
And then that will be returned. So every time you call next, it gets the value from the parent iterator, goes through our mapping, and then it's returned. Well the problem is that's not a vector. That's not a list. It's an iterator. It's just this right here. We need to convert it back into something we can use, such as a vector.

[00:10:39]
So a collect function, we will actually write out effectively what collect does here in just one moment. It will become much more clear what collect is doing, but for now you use collect to go from an iterator back into some sort of structure. I believe they call it a catamorphic operation but we're not gonna use those terms around here because that just feel like you get beat up if you say that.

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