Check out a free preview of the full Rust for TypeScript Developers course:
The "Reading Shapes from a File" 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 reads shape data from a file and codes a new Shape enum. The Points and Contains traits are implemented, and adjacent lines in the data file are checked for collisions.

Get Unlimited Access Now

Transcript from the "Reading Shapes from a File" Lesson

[00:00:00]
>> ThePrimeagen: What if we wanted to read our shapes from a file? And so I think this is kind of an important experience here, cuz this is gonna really put all the things together that we've done, every last item. So we need to implement a trait called FromStr that allows us to convert a stir into the type we want.

[00:00:17] Remember our little parse method right here? We saw that earlier converting a String into a usize. All right, well, we can actually implement that. And so before we do that for both Circle and Rust, I'm gonna add a library, we haven't added any library, using cargo add anyhow.

[00:00:32] Anyhow allows you to effectively just have super easy error handling. Again, I kinda described the difference earlier, cargo add anyhow. There we go, we now have anyhow, fantastic. So let's use Anyhow and FromStr to implement a Circle and Rectangle parse. So what I'll do is, let's just say that for a Rectangle it's just four integers, or four usizes, and that will be our Rectangle.

[00:00:58] For Circle it will just be four, I guess we can do f64, it's all the same, right? It doesn't really matter. So let's go to our shapes. And I'm gonna go down here on our Rectangle all the way down to the bottom. I'm gonna go like this, impl FromStr four Rect.

[00:01:16] And now by doing that, it's gonna give us something nice. It's gonna give us an associated type error and FromStr stir. So I'm gonna go like this, the Err = anyhow: :Error. Yay, now if you remember earlier our question mark operator, by having a function that returns an error or a result object, we can use the question mark to just unwrap things really, really easily.

[00:01:43] So this is what I'm gonna do. I have a reference to string and I need to return out a Rectangle. So I'm gonna go return Rect, and let's see, how do we want, I still have Copilot here, disable that. So let's do this. Let's go, let parts = s.split, on whatever we call this, a whitespace.

[00:02:04] And I'm just gonna collect it into a vector.
>> ThePrimeagen: There we go, I collected it into a nice little vector. We have a vector of str so I just kinda got all the blue parts out of it. If parts.length does not equal 4, I'm gonna return an error of anyhow.

[00:02:28] Anyhow, we can just say whatever we want, bad or rectangle.
>> ThePrimeagen: Bad rectangle, does that sound good? Bad rectangle, yeah, not the best error, we could have done better, from str. There we go, look at that. Still not that great, but it worked out. Then inside of here we know that there's four parts.

[00:02:50] So just kinda being lazy about it, I'm just gonna go like this, parts

[0].parse and then question mark operator. Cuz remember, it's gonna return a result. Since we're in a function, that is a result, it's just gonna pass everything right back out, fantastic. And then we're gonna do the same thing again, y, 1, width, 2, height, 3 and there we go.

[00:03:14] And now we have everything except for it's expecting a result. We just have a rectangle. How do we make a rectangle into a result of rectangle, anybody?
>> Speaker 2: Tuple?
>> ThePrimeagen: Not tuple, Ok, right? It's the value part of a result. So now it's a result of value rectangle, all right?

[00:03:35] So there we go, we've just parsed out a string for our rectangle. Pretty fantastic, right? So I'm gonna honestly just highlight this whole thing, I'm gonna yank the whole thing. And we're gonna go over to circle, go to the bottom, paste it in, replace Rect with Circle, implement or import in FromStr.

[00:03:55] And so now we have to do the same thing except for it's no longer a rectangle, it's now a circle. So let's make that to 3, bad circle from string. Implement Circle, take away this last one. Radius, look at that, done. We're just cruising right now, aren't we?

[00:04:14] All right, so there we go. We have our circle from a string. We have our rectangle from a string. Okay, this is pretty exciting. This is pretty exciting. So now, okay, here's all this, there's all that you can grab from the website. So let's create a file. I'm gonna highlight this little guy right here, highlight [SOUND].

[00:04:31] And I'm gonna create a file and I'm just gonna call it shapes. Shapes, paste it in, no new lines, there we go. Rectangle, circle, circle, rectangle, and I just realized this file is wrong. Circle should only have 3 points. That was a poorly formed circle, we would have probably got an error.

[00:04:48] It makes me sad we almost messed that up. All right, so there we go. We have our nice little file right here called shapes, and I want you to read this file. Well, I want me really to do this. I'm gonna read this file. I'm gonna create an enum to store both Circle and Rectangle.

[00:05:04] And I'm gonna read each line, I'm gonna use split_once, and I'm gonna find any adjacent collisions, all right? So we're gonna get a list of objects in or shapes in, and I'm gonna find any of them that are adjacent and colliding. So, what is adjacent? If they're one line away from each other, kinda fun, right?

[00:05:24] That's kinda fun little challenge right here, a little to launch right at the end. So let's start with reading a file. That shouldn't be too hard to do, we've done it before, I believe in everybody here. If you don't believe in yourself, well, I'm sorry. All right, so let's do a result of the unit.

[00:05:42] Remember the unit? And I'm gonna go use Anyhow, oopsies, anyhow: :result. So this is just the fun thing that I don't even have to specify an error if that's what makes anyhow so dang fun. I delete all this and I'm gonna go return, Ok unit. So there we go.

[00:05:58] Now I can just use the question mark operator really easily and just start unwrapping the things I want. Cuz if our program fails in any way, the whole program is gonna crash. I don't care at this point because it's a toy program. We can let it crash. All right, so first, let's read the file.

[00:06:15] So let file = std::fs::read_to_string, and it's shapes, if you remember that. And then I can just do the little question mark. Look at that, now we just have a string. If it was an error, the error is returned, if it's a value, we get the value. Yay, question mark, no, if error equals nil, there we go, or does not equal nil.

[00:06:36] So now that we have the file, I wanna go through the file and convert each line into a shape. But what's the problem? Well, if we look at our shapes file, it's rect circle. It doesn't quite line up with how we parse from a string, right? Cuz the rectangle parse from a string requires four things, and they're all numbers.

[00:07:00] Whereas this one, well, there's additional information. So we almost need something to kinda wrap this. So I'm gonna go like this, enum Shape, it's gonna be a Circle with a subtype Circle. Or it's gonna be a Rect with a Rect subtype. Look at that, okay. So now we can kinda do this heterogeneous list to do that.

[00:07:21] And so that means we can do something like implement FromStr for Shape, and now we can actually implement FromStr for it. Which means we can do something like this. I can go let type, we can't call it type. We'll call it (shape, data) = s.split_once.
>> ThePrimeagen: And split_once returns us out, what is it?

[00:07:51] An option, all right. And we'll go like this, split_once(" ").unwrap_or( Let's return out, how about an empty string, and an empty string. There we go. That's good enough to me. That's all we need to do because, if we didn't get data, that's fine. We will just error it out in our processing.

[00:08:12] So that means shape is a string, data is a string. We handled the option, because you may not be able to split the thing once if there's no white space. That's why it's an option. I think that makes a lot of sense. Yeah, Yeah, all right. I'm gonna do the best thing ever.

[00:08:29] I'm gonna do a little quick match statement. Now here's the problem. Match statement now is getting passed in a stir, which means we need to cover every single possible string ever created in this match statement. So let's start with the easy ones. How about rect? Is that what our file is by the way?

[00:08:46] Yep, correct and circling. So, if shape is recked, well, let's return an Ok because remember, we're we're dealing with a result, so we need to keep it as a result. So an ok, shape, rect. And now we have access to the data. So we can go data.parse. Now what does data dot parse gonna return?

[00:09:14] Well it's going to return the correct type, because we're telling it by how we're coding. It knows that it needs to return a rectangle, but it's going to be returning a rectangle. We're gonna be turning a result of rectangle. So, throwing a little question mark right there, bam, bam, bam.

[00:09:29] And we are looking good to go. Now we got to do it again for circle. If the thing is a circle, then let's just do a circle. And again, we're good to go. We have a right here. Lastly, let's match every other possible string handed to us. And let's return an err of anyhow, anyhow, bad shape.

[00:09:57] There we go, we could technically like format the string, such that we could actually include what you provide us. We could really do something a little bit better here. Well, let's go up here, and what is our error type? Well, it's an anyhow err. All right, fantastic. Look at that.

[00:10:17] So now we can actually parse out all the shapes possible with this cool little trait implementation on an enum that every single type inside of it also has an enum from string. So it just, it just works. Yay, right? Is that a yay? That's a yay for me.

[00:10:35] Okay, so let's see, what are we gonna do next? Next, we have to be able to make these shapes collide, right? Because that's another part of us, so we need to check for adjacent collisions, how can we do that?
>> ThePrimeagen: What does our blanket implementation need? Points, contains, so, let's implement points, for shape.

[00:11:05] Well, it's really not that hard, because there's only two possible shapes, right? So let's match self.
>> ThePrimeagen: If it's a circle, circle dot points.
>> ThePrimeagen: If it's a rectangle, it says r dot points, and let's return each one of these. I know or I can just hear the rest stations getting so angry.

[00:11:33] Don't you do that, there we go. Look at that, it's pretty straightforward, right? I bet you there's a way we could actually make it. I think there's probably a way we could do some sort of sweet blanket implementation that makes this possible, without having to manually write all these out.

[00:11:46] I'd probably have to think about it and play around with it. I bet you there's a way we could just make this automatically happen for us. I just don't know how to do that. Not yet, all right. Points contains, we'll do the same thing, contains for shape. Well, again, all we need to do here is, let's see what is contains here.

[00:12:06] Get out, get out of here. Contains requires us to do this. So now all I have to do is I'm gonna just jump up here, grab that one, jump back down here, paste it in. And just simply jump over here, and contains, points, point, there we go. C contains point, point, there we go.

[00:12:27] We've now implemented points and contains, which means what happens with their blanket implementation? Well that means we can compare a circle against a shape. We can pair a shape against a shape. We can pair a rect against a shape. We can do any cross section because we have defined our blanket implementation that way.

[00:12:47] So this should just auto-magically work. Which is kind of neat, I think it's kind of neat. I think it's kind of neat.
>> ThePrimeagen: All right, well that was kind of fun for me to say. Anyways, so now that we have a string and we have the file, let's split it by line, right?

[00:13:06] All right, so each line, now what we need to do is for each line we need to turn it into a shape. Something like this, filter map, and x dot parse. And let's give it a shape. So that way it knows to use shape and then go, okay.

[00:13:24] If it errs we're just gonna ignore the err and just keep on trucking along. So we're just gonna only successfully parse, we're gonna only parse successful shapes. There we go, that seems about right. So now we're like mostly through this. We have our lines, we have our filters, so how are we going to do the adjacent collisions?

[00:13:47] Well, first let's collect, and we'll collect it into a vec. Remember, we can do that and so look this, let shapes. So remember that underscore is able to go okay, well, I'll just use shape right here. I already kind of know what's going on since I can kind of guess what's happening.

[00:14:05] Here you go, here's your proper thing. So it's just avoiding me having to write, the type multiple times. There we go, so now we have our shapes. So now let's do our adjacent collisions. All right, so this is where the fun is gonna happen. Let's go like this, shapes dot, let's see, shapes, iter.

[00:14:25] Now we create an iterator. Remember, this iterator doesn't consume, it just refers to. So underneath the hood, I don't know what the exact implementation does, but it just takes a reference to vector and turns that into an iterator effectively. You could imagine that's what it does. It already has a vector or a from, or in pull into iterator for reference of vector.

[00:14:48] So probably that's my guess of what it does. What it exactly does, I don't know. So there we go, we have shapes iter. Now let's do this one, are you ready? We're going to skip one, who here? Yeah, this is feeling good, right? So we don't do the first one, then let's zip it with another shapes dot iter.

[00:15:04] Yeah, we're getting wild tonight, okay? And let's do this one. Let's take shapes dot length minus 1. So now we have two iterators zipped together. One going from 1 up until the last one, or 1 going from skipping one all the way to the end, the other one going from 0, skipping the last one.

[00:15:27] So they're offset, so that we can do all of our adjacent collision testing. Fun, all right, so now, let's jump in here and go filter, which is going to be a b, and go a b, collide b. Well, we just kind of did it. Just kind of went and so now we have this thing.

[00:15:48] But look at what happens here, it says hey, We don't have points for a reference to shape. The trade points is implemented for shape. So I mean technically we could potentially just go like that. Just throw these things on here if we really wanted to. And we'll just get that.

[00:16:03] So implement it for the reference, not for the actual item. And there we go, we have this unused filter now. And if we really wanna we can just simply collect it into a vector of some sort. 7UP, let shapes equals this. We'll just redo this or actually, we can call this collisions.

[00:16:19] There we go. No. Yay. But we need type annotations. It doesn't know what it is. So wherever you wanna put the type annotations, if you wanna use the TurboFish operator.. Go for it and there we go. This will just have enough information to know, because right now it knows that at this level right here, it is getting a tuple of shapes.

[00:16:41] So now I'm just collecting them all, or a tuple of reference shapes. So if we look right here, we'll see tuple of reference shapes. Fantastic. And now we can print LN. Let's see, what is it? Shapes? Shapes doesn't implement the debug trait. So instead of just doing that, let's just, can we jump up here and just implement debug on it?

[00:17:06] I'm getting kinda lazy here and we're kinda running out of time. I don't think that's gonna work out because these ones don't implement the debug trait. So I'll just jump in here and implement the debug trait. I know we did that whole displaying. No let's not do that.

[00:17:18] It's not worth it. Hashtag, it's not worth it. Jump back. Let's not fall victim to laziness. All right, here we go. We'll just have to do Impl Display for shape and let's just what do we wanna do? Take that guy and just do the exact same statement. Take a match statement, paste it in here.

[00:17:44] If it's a circle, then we can just simply do let's see, what do we wanna do we wanna do right bang for matter c.
>> ThePrimeagen: Did I not implement display for circle? Dang it! We didn't even do implement display for circle. We fell apart here, right at the end.

[00:18:05] All right, we're gonna keep on going though. We're gonna keep on going. We got this. Never let them take you down. All right, jump over in here. Jump to the circle. If meant display for circle. All right take it in here return right bang F circle. And we're gonna have our two points right here that's where we are with the radius of that.

[00:18:27] So we can go self.x, self.y, self.radius, and boom. Now we have this. This has it. Hey area, we have it. An unused import, get out of here unused import. And so now we have all of it all the way down. And look at this. We have shapes, which means we can now, instead of collecting, we could just for each is kinda nice, right?

[00:18:51] I kinda like for each. I was just gonna print them all out anyways. So we can go like this. So, print ln A, or collides with that.
>> ThePrimeagen: Look at that big beuty right there. And in the end, this is all we have. That's a lot of stuff that just went on.

[00:19:13] And now we can use some pretty cool little iterator combinations to end the day finding all the collisions in the list adjacent to each other. No one's excited. I'm excited. So let's go cargo run. And look at that we got absolutely nothing happening. Why don't we get anything happening?

[00:19:30] Did we break something right here? We might have actually broke something, so let's go print ln shapes. Let's find out. Let's just throw in here. Man, I'm no longer awesome. I thought I was awesome. I thought I was a brilliant person. There we go. Do we not have?

[00:19:46] I bet if we go look at our shapes quick little perusing of those shapes. I don't think any of them collide with each other. Yeah, I don't think so. So let's go like that.
>> ThePrimeagen: Because that cloud was something? That should collide with something.
>> Speaker 3: But it doesn't collide.

[00:20:10] So three collides with two with the first one, but it never gets compared to that.
>> ThePrimeagen: You're right. Yeah, good call. My own devices just got me. So we'll put this at 18. There we go. Got them. There we go. So we now collided circle with circle and rectangle with circle.

[00:20:29] Look at that. How could that even be? What are the chances that I got had by my own data? Ugh, it feels so bad.