Transcript from the "Projector Operations in Rust" Lesson
>> Let's start with good value, is that fine? And of course, I just realized I forgot to add the word pub. You can't be doing much without that. And I just did this and go style. And I did it and go style twice. You know too much go and you forget how to write code, there we go.
[00:00:15] All right, you should be using snake case, it was gonna yell at me about snake case at any moment. There we go, beautiful. So, we're gonna do get value now and get values pretty simple. Remember is of course it takes in a key which is gonna be a string and it's gonna return out an option that is a string, right?
[00:00:32] Because we may or may not find this value correct? Correct, all right. So this currently exists in the static space we gotta go like this itself awesome. So now we're actually on the instance or on the struct instance itself and we have reference to that item. So now I can do this simple like self.data.projector and now start doing the operations we need to do.
[00:00:57] So if you remember we have to go through all of our directories starting with where we're at, and walk all the way up the tree until we get to the root directory. Now Russ does this a little bit different than the other two? And some would argue it's nicer.
[00:01:11] But it's gonna take a little bit of playing around with types. So let's kind of work our way there, we're gonna run into a couple errors but overall we're gonna get it done. All right, beautiful. So let's first start with how do we do this? Well, if you look at our present working directory something like this, let mute current equals self.present working directory.
[00:01:43] Oops sorry, config.present working directory. So what is present working directory? It's a path buff. So in the other languages we use a method like dirt or dirname kind of really mapping to the Unix style in rust. We don't have that, instead what do we have? We actually have parent right there on there but what does it return?
[00:02:02] A returns an option of path which is pretty nice because that will technically return an option to a reference path and what is nice about that? Is that allows us to know when we've hit the limit. Meaning that it actually says, hey, there's nothing left, there's no more parents at the root.
[00:02:18] Whereas dirname just keeps on returning, the slash directory over and over again. So a little bit nicer API, I would say, but we now have an option we have to handle. So let's try to kind of do this one step at a time. So that means if we're clever about what current is, we could probably have a really nice little walking style.
[00:02:39] So let's try to think about that. What I ideally want to do is to be able to like this while curr.is_some right it's like it is a value while is a value. We should do this. Now we could also get real clever, I believe, and do this, while path = curr, right, and really get super duper clever right here.
[00:03:02] And while we can still get a value out of our current directory, we'll keep on going. So I think this is pretty nice, pretty clever. But that means we need to do something here. So we can go okay, well I can turn this into a sum right? And so now this is sort of working correct?
[00:03:20] We are now grabbing out that value and doing something. At the very bottom of this just to kind of get rid of that error. I'm just gonna return down really quickly. So rust analyzer doesn't keep harassing me like this. But as you can see we're now kind of in a good place I like how the code is looking, it's feeling good what is p?
[00:03:34] P is a path buff fantastic. So that means I should be able to like this cur=p.parent right? Well we have a slight problem here. What is the slight problem? Well what is parents return? It returns an option to a reference path whereas what we have is a path buff, right?
[00:03:54] Like we're so close yet we're just so dang far. So what happened if we jump up here and we go to or as path. What just happened there as path is a lot like as stir. It's a reference to the thing. The path buff is obviously the thing.
[00:04:12] As path is the reference to it. So now that I've done that I can do this nice little loop. I'm literally gonna walk the whole thing and we're pretty much good to go. Right? This is fantastic. This looks really nice to me at least. I'm happy about it.
[00:04:26] And so that means we can now start trying to grab values out to see does it exist on our projector. So let's go self. And of course it's on the data projector. So now we're into the whole HashMap working. So in our head without looking at any documentation, I bet you there's probably like a get an insert, a remove something like that.
[00:04:47] Right you can see a get, you can see a remove, you can see an insert, right? It has pretty much all the things you would expect. It has a contains. If you really want to go about this method. So we can start there. We can kind of just play with something that's a bit more procedural.
[00:05:04] We can try different things so we can start with this right now contains key right? We may change it here shortly. So I can go if projector contains key P well, then we know that there's something there right? But I'm not gonna lie to you that's it's a little annoying, right?
[00:05:21] Can we at least agree that's kind of like now I have to ask for the value out of projector and then it's probably gonna be an option. And then [INAUDIBLE]. So let's try something maybe a little bit different. Can we get something different out of here? So let's try go like this.
[00:05:36] How about sum, I don't know what the value is going to be. And let's try self. Data, projector get and then let's pass in peak all right. So, what do we get out of here well it's gonna say hey mismatch, types right what is key return it should return an option of value, I forgot the word let there we go, so what is V?
[00:05:58] Well V is a reference to the HashMap so this is good. This is kind of what we wanted anyways right? We got the thing we wanted. It's already unwrapped for us this feels really nice. Now obviously, we could have used in an event or map on the options object that's another option we could have gone with, but we'll keep going pretty procedural on this.
[00:06:14] So we have the HashMap. So I'm gonna just like in go, we'll just call this thing dirt. And now we can do something with this, right? We can say hey do you have this value, right? So we're gonna program this identical to how we did it in Go.
[00:06:29] So we can go let sum value Do dirt.gets and then we can use the key, right? The key that is passed in but now of course what does it saying hey, what does get need? Get does not need the concrete object string cuz that means every single time you called get you'd have to clone off your string right?
[00:06:50] It only needs a reference to the left hand side right or the key value so I can go like this hey, are the key type again have a reference to the string awesome I have the value. So if statement does happen really I found what I need to find, and what is value?
[00:07:07] Well, value is an ampersand string. Okay, we could probably do this, why don't we just try jumping up here turn it into one of those and then have let out = none. Down here, do a little out down here and just do five up, we've got to make this thing mutable.
[00:07:27] Always make it mutable, right? Go out = sum value right? There we go. This is what we would expect, that means the outside of our program couldn't call in. If this happens, awesome, we have a value. So this is one way of just doing it. Obviously, when you have options, you have functional things, there's a lot of different ways we could do this.
[00:07:49] This just feels like a very if you're familiar with typescript or go this feels like a very similar approach and we're gonna stick with this. So awesome We've done it, hurray. Let's do the next method, which I think is probably arguably the hardest of the methods. But I think rust is gonna really help us out on this.
[00:08:08] So let's do public function get value all, right. So this is the one where we take nothing in except for of course a reference to self, and we the produce out a HashMap of string string. Now there's going to be some copying here and that's okay, right, we're gonna be okay with this.
[00:08:26] We're not trying to go with the most performance driven piece of code, we're just gonna do what we have to do to get things done, it'll be fun. So let's erase pretty much most of this. Now remember, what do we need to do first? Well we need to get the list of paths, but we need to walk it backwards afterwards, so let's do that now.
[00:08:49] So what I'm gonna do is I'm gonna like this, I'm gonna go pass equals a vector. Now I don't have to give it a type because rust is just gonna figure out what the type should be. And then in here I'm just gonna go like this, path.push current, all right.
[00:09:08] And so what's happening here, it says, hey, can't find current, I must have accidentally deleted current from down here. Let's just paste it right here, beautiful. So it figured out what paths it's supposed to be based on how it's being used. You can see right here it's a vector of option path, we kind of we know enough that this it's better than that here.
[00:09:25] Actually let's go like this, p, sorry, wrong one, there we go. It's a vector of reference path. Awesome, so we have the references to each one of these paths. So now all we really need to do Is walk backwards through this vector. And each one of the paths, check to see if there's a HashMap available, and then merge the HashMap into some greater hash map and be done.
[00:09:49] Everyone good with that? All right, so let's do that. Let's go like this. We're gonna use a for loop this time, it makes it a little bit more fun. So let's go like this. For path in pass itter reverse. Awesome, so now we're just gonna walk backwards through this.
[00:10:08] And so what is each path, of course? It is a reference to a reference to a path. And that makes sense because I didn't do into iterator, if I did into iterator what happens to my paths? We consume it. So we could do that, we could go into iter, fantastic, and now we just have a simple reference to a path.
[00:10:28] This also works out, we'll just do it this way, why not? Let's just consume that. We don't need that vector afterwards, let's just destroy it, we don't want it, right. And you can tell right away that if you tried to do get 0 it should say, hey, you can't do that, let A equals this, right.
[00:10:44] It should say you're not supposed to be able to do that, my goodness, rust analyzer only operates in steps so won't actually give me that error. All right, whatever, I'm not going to make that force the error. Okay, so we have it here, we have the path. Let's go and see do we have this in our hash map?
[00:11:04] So self data projector, and then we can do a get and we can provide our path, right, cuz it is a reference so it uses that niceness right there. Everything's looking good. We have our beautiful item, here, returned on. There we go, just it needs to be quiet.
[00:11:25] All right, there we go. So we now have the value out. What does it return though? It of course returns an option of HashMap string string. So let's kind of use a little bit more optional stuff on here. We can map this thing, right, we can actually do an operation if this thing is something.
[00:11:42] So we have two different options here, we can either do an if some map equals this, Or we could do the exact same thing. I'm just kind of showing you guys some of the options that are available, and do a map and provide a x right here. As you can see, this is a reference to a HashMap string string, and of course as you can see right here, this is the same reference to the same thing.
[00:12:12] So how do you want to do it, right? It's kind of up to you. There's benefits to always staying in options, which is really, really nice. This is just a simple and explicit so you get to choose what kind of stick and a little bit more procedural for these parts.
[00:12:24] So we'll just keep on going down that path right now. But now we need to return something, and we needed a way to be able to merge all this stuff together. So let's create a let out and make it equal a hash map niil, all right. No need to give it any types because we're just gonna go out, it'll be able to assume it's types, fantastic, and it's gonna be a type string string.
[00:12:46] So we now have a string string one, and now we need to just merge that into our new HashMap. So how are we gonna do that? Well hash maps come with this beautiful method called extend. Extend, let's see, where are you? There we go. And what does extend do?
[00:13:02] Okay, I'm gonna zoom it out one bit. Effectively what is extend do? It extends a collection with the contents of an iterator. Awesome, so that means I should be able to go map dot iterator. So I've just extended this thing out. But what happened? We're seeing some issues, right.
[00:13:24] You can see right up here on top, I can't use my, I'm trying to use the touchscreen. Yeah, look at that touchscreen, go, it's way off. Anyways, you can see right there that it's expecting something. Get down here we have the error. So this is rust analyzer trying to give you the full nine yards of what's happening.
[00:13:40] So it's saying, hey, this is where the error started, and this is where the error is resulting in. So let's go down here, let's look at the error. Mismatched types, expected a HashMap of string string but found a HashMap of reference to string, reference to string. This would probably work out, so let's just hop back up here, do that, right, because we are just referring to these string objects inside this HashMap.
[00:14:24] And so let's go down and let's do the last two of the greatest methods ever, which of course are set value. Look at that, it even gives me all the way up to here. We're gonna set value key string string or key value string string, and then we need to remove value.
[00:14:40] So setting value shouldn't be too hard. Again, we're using the HashMap. It can be a little bit goofy, so let's go like this. Let's see what we have available cuz maybe there's a better method we could use to make this a little bit more fun. All right, data projector, so we could get mutable, so this means it returns a mutable reference for us instead of just a reference.
[00:15:02] Obviously a mutable reference would be very good cuz if there is something underneath, we need to be able to do something to it, correct? Correct, but we can try something a little bit different. Let's try, how about entry. So entry is fantastic, so given a key corresponding entry right here.
[00:15:21] So we can actually go, hey, I need an entry into this hash map, and we'll pass in, I believe we'll pass in the key, it will consume the key. And so now we can go, or Insert, and now we can actually grab and create something right here. I've never tried or default but right away I have a very good idea probably what it does.
[00:15:43] My guess is that it does it calls the default trait on the object, as long as it has the default trait implemented it should just be able to do that. So ensure the value is in the entry by inserting the default value if empty. Awesome. So by doing this, let's just go like this, let's just see what type what do we got here, right?
[00:16:01] All right, now it's saying we actually have a mutable reference to the HashMap string, string. So this is good. We could actually do something to that. So let's erase that. We don't really need to look at that. Now obviously, something's going wrong here. Found a struct path buff, but we provided a string.
[00:16:18] So my bad on that one, self.config.pdw. All right, there we go. So if there is a present working, grab the present working directory. If there's nothing there, put them into default value. Let's back this thing up a little bit. Awesome, so now we have something we need to insert into, right?
[00:16:40] We have either an empty HashMap or we have one that is already existing there. Something like this. I'm gonna use insert and let's just pass in key. Let's just pass in value. And guess what? What do we got here? [LAUGH] Well, I guess we're done. How great is that?
[00:17:00] So, pretty fantastic. I'm happy about that. It actually is a bit easier than both Go and TypeScript, right? Because we are actually, we can do both the getting and inserting operation at the same time, and so it's pretty fantastic. I like it. I think it's actually pretty fun to play with.
[00:17:18] The hash like I said, the HashMap interface is pretty extensive. It's a little bit more than what you would expect. Definitely takes a little bit more time to master, but I'd say, other ones from other languages. Other languages are really dirt simple. So, last thing we want to do is do a remove value, which is just gonna take in a key.
[00:17:36] And now we're just gonna do the exact same thing. We're go have to get and to delete. So, we could this where we do the exact same thing, where we actually grab the entry, or we default it and we remove it. So to keep things simple, we could do that, but there is one problem of course with that.
[00:17:56] That means if this entry didn't exist before, we would create the entry as an empty object inside of it. Is that a problem? I'd say probably not a problem. But why not? Right? I personally don't think it's a big deal. So let's just throw in this, and of course what happens here, it's saying, hey, you want to remove something.
[00:18:16] I don't need the value itself I just need a reference to that value. So a lot of the times how you choose to do these interfaces are gonna affect especially in testing and all that. So, I mean, in some sense, it almost would be a lot easier to have this correct.
[00:18:33] Because if we had that, what do we have right here? Well, it just it just kind of works out. And you can imagine when we're testing, I can just pass the string in, as opposed to having to call the string constructor or do to string clone it out into a different object, right?
[00:18:48] So there is a some value in kind of thinking about how you keep your signature. So for remove value, we'll keep it as an ampersand stir or just what they just call it a stir usually. So that's just a string reference, there you go. Fantastic, right? Now, we either do the testing portion of this.
[00:19:05] I believe we got everything we've been, I think we got everything here. I think we're gonna win, right? I think we're gonna do first try every time all the languages, so let's do exactly what we'd normally do for Rust. We go down to the bottom of the file, so bring it up.
[00:19:20] Actually, before we do that, let's test how many lines did it take us to do that? Starting at 72, or that's our ending at, and our starting at is at 24. So that might have even been shorter than the TypeScript version. And I believe we could actually make it even shorter.
[00:19:36] So here, let's go to TypeScript, jump into here. What are we at? 79 to 23 versus 2472 or 70 was that 72? Yeah. Yeah, look at that. It's like six seven lines sorter and look they way I did a lot of things right there, right? We could whoa, it's way shorter now, right?
[00:19:56] We gotta you know, we can really do some great things there. But that's pretty cool, right? This is a very hard language. And we did it very procedurally, very straightforward and it's actually fairly terse, right? That's very impressive for language like this. If you were to do this in C++ it might take a few 1000 lines of code, but that's pretty standard because I'd still be defining all the way variables, but we're doing it in Rust.
[00:20:16] It's fantastic I love the implicit nature of being able to figure out these types like I've been showing you along the way. How we can kind of inspect the types, know what they are, and make decisions based on it.