Rust for TypeScript Developers Vector, Tuple, & Struct
Transcript from the "Vector, Tuple, & Struct" Lesson
>> So we're gonna start doing a little bit of learning off the rip. We're gonna start talking about a vector just because a vector is the most analogous data structure we possibly have to TypeScript. So in TypeScript land, a list is defined with two brackets. And in the Rust land, effectively the same list is defined as a macro vec brackets.
[00:00:22] So these two are functionally equivalent. By all sense, they're a non-mutable array that contain or technically a non-mutable list containing 1, 2, 3, 4, 5. I'll be probably using the word array or list interchangeably, but technically, an array is something that can be allocated on the heap, it has this static structure type, bla, bla, bla, right?
[00:00:44] A list is something that can grow. Sorry, it's just I feel obligatory to mention that. Nonetheless, here you go. Two equivalent items, it looks pretty similar, right? All right, so mutation you have all the same things, again right now we're just going lightly over it, we're gonna start coding here in just one second.
[00:01:02] But mutations, if I go const a, I do this, I can just push in 6. I know I just made a const, yet I can mutate it, right? In Rust land, you can't do that, I can't just say let a equals this and push in 6. It'll say, hey, that's not mutable.
[00:01:15] I need to make sure I do let mut a, then I can call push. It's pretty much the exact same thing as TypeScript. This is valid Rust, there's nothing invalid about it. Exact same thing with pop, I can do pop as long as I have mutate on, I can pop.
[00:01:30] Pretty much the same piece of code other than this whole vec thing right here. Accessing data in TypeScript land, you do this. Now, there's something very interesting about this. What is the return type of item? Can anyone tell me what the return type of item is? Or sorry, not the return type, the type of item.
>> Probably the default integer.
>> Yeah, it'd be a number, but it's not just a number, it's more than that, it should be more than that, it's number or undefined. In this situation, because we're super smart humans, we can see right here, we're safe to do that. But in the reality is when ever you access an array, the thing in there might not be in there.
[00:02:37] I don't know. All right, so in Rust land, we have kind of a couple ways to do it. We have the old definition right here, we could directly access its elements. This does work, you now get a reference to T. But if you go outside the bounds, you explode.
[00:02:52] Or you can do it the more kind of what I'd consider like the safe way, you can do.get, which returns an option of T. Option, of course, is Rust's kind of version of null/undefined. It will give you something out that you now have, but it just may be nothing.
[00:03:08] So you have to deal with that up front. You always have to take this idea that something may not be there every single time. So I always suggest using.get, unless if you have good reason to use this. Again, there's the smart programmer version, which is I created this array with three items in it, and I'm gonna access the middle one.
[00:03:23] Do you really need to do it the other way? Probably not, but you get the idea. Gentle reminder, you're gonna forget everything I've already said. This is kind of boring, right? We're not typing, we're only using your eyes. I like to type, don't worry, we're gonna do that very, very soon.
[00:03:37] I just have a couple more items I wanted to make sure I kind of get over. Again, here's another not necessary thing that exists in TypeScript, but kind of exists, we have a tuple. A tuple is of type parenthesis I32, which is a signed 32-bit number, capital S string.
[00:03:55] And notice this is how I defined it. It's just two things next to each other. If you were to really think about it in memory, I believe that thing would take up 32 bytes, or that would take up not 32 bytes, 28 bytes, probably 32 bytes. I have no idea how many bytes it probably will take up, but this thing is 4 bytes, and this thing has a pointer, a length, and a capacity.
[00:04:15] And so of course, the data hello does not exist on the stack, it exists somewhere else. Just kind of something to always think about for a moment. I'm gonna say this a bunch of times even though we're not really gonna get into it, I just wanted to mention it.
[00:04:26] So the nearest equivalent is this right here. You have a list of two items, 5 and hello. So they're kind of the same, but they're not really the same, you can't push onto a tuple. A tuple is a fixed structure. This is obviously an unfixed structure, you can do some things with it.
[00:04:42] You can cast it as const, but still it's a little different. Second, you can also do a pattern matching or just like destructuring for tuples. So if I have a right here and it is 5 and string, I can also assign a like this, notice how pattern matching works.
[00:05:01] I literally type out its structure, which is in a tuple, it's two parentheses, and then I give variable names for the insides. So those insides have been pulled out and now handed to me, and I now have access to my number and my string as opposed to tuple.0, tuple.1.
[00:05:21] Pattern matching even works on functions. So if I were to define a like this, I could actually define a function that's pre-destructured as opposed to doing it within the code. So I have my number, my string, and then I say it's type, which is an i32 and string.
[00:05:34] So that means I have now access to the insides even though I've just passed a like that. Structs, again this is kind of we're getting into this territory where there's not a direct one on one. Struct, you can define any which way you would like to. There you go.
[00:05:48] I have any property you want on here, you can define them. Structs can refer to other structs, it's fine. Referring to yourself in a structure is not allowed unless you wrap it in an option. It would make sense if you think about memory, how would you lay out a struct that refers to itself in memory?
[00:06:03] Well, you'd have a struct that's say 8 bytes long and then a reference to itself. Well, that's 8 bytes long, but then that references itself again. Well, that's 8 byte long and you'd keep on going, ad infinite until you explode. So you have to have some rules around it.
[00:06:19] You can also destructure with structs. Here's a function that takes in a MyStruct, you can tell by the type right here. And as you can see, I can say, hey, MyStruct, and I want property y and z, and forget the rest. So now I can just do y and z.
[00:06:32] So pattern matching even works at a struct level, which is, there's some really cool problems you can solve with pattern matching. That is pretty magical, but we'll get there, there's one that just recently blew my mind. So I can just do this and I can destruct like that.
[00:06:48] I just want x out of it, I just want x and or y and z out of it, I want all three out of it, I wanna do an if let. An if let allows you to pattern match even on an if statement, so x only becomes visible inside of this if statement.
[00:07:03] So pretty cool. So you can do these kind of nice little ways. It works really, really well once we start getting into options or errors, is we can kind of pattern match on the if level. Super fantastic. Pattern matching is just out of this world. I always feel like I wish I had more ways to use it, and I always feel like I'm not using it enough.
>> Does Rust detect these types then?
>> What do you mean detect?
>> Does Rust infer? So yes, this is using inference right now, so if I were to take this exact code, drop this in my editor and say, hey, what's your type? It would probably say something like squirrely brace integer comma string.
[00:07:45] That is because it hasn't quite concretely defined what type of integer this is, cuz it doesn't know how I'm using it yet. So if I pass it into a function that requires an i32 in string, it would then go, okay, I can walk that chain backwards and see that you've created something without telling me what type of integer is but you use it in i32 sense, that's an i32.
[00:08:04] So it has really strong inference, very, very strong, much, much stronger than TypeScript, it's quite wild.
>> How different are structs from objects?
>> Structs and objects, it's about how they're laid out in memory that probably makes the biggest difference. And then number two is the ability to mutate.
[00:08:39] Delete fu.bar will delete bar off of fu. And so in Rust land, a struct is a blueprint. You cannot deviate from the blueprint, you can't just be adding properties or removing properties at runtime. And that, of course, is due to how memory's laid out.
>> Does the compiler give you any information about .get versus the brackets of like, which to use or what?
>> The compiler will always tell you what's going on, meaning that you can go over and highlight on your variable and it'll tell you the type. There's some things that are up to you to know how to use it, so if you're directly accessing something, you take the risk of directly accessing something.
[00:09:21] If you just wanna be safe, always use the safer methods. That's why they're there. That's also it's very nice that they have these unsafe methods, because sometimes you just know better than the compiler, and that's okay. No need to go through all the hoops if you're doing something precisely.
[00:09:35] But often, I find that I rarely use any form of indexing like that, because either I'm doing a for loop or some sort of iteration over it, or I don't even access the elements directly myself. I'm doing some sort of dot map on it, and now I have access to each individual element as it goes.
[00:09:50] And so you don't often do that type of coding or at least I find that I don't often do it.