Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course

The "Fissures Rust Solution" Lesson is part of the full, Polyglot Programming: TypeScript, Go, & Rust course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen live codes the Rust solution to the Fissures problem.


Transcript from the "Fissures Rust Solution" Lesson

>> So let's get back to here. So how do we specify the type error from a from string? It makes sense also that there is a potential for error. Cuz if you parse something from a string, what if the string does not match? Of course, there has to be some sort of error.

And so the other option would be to use an option return nothing or something. But I think an error is probably more appropriate cuz you can give some context around it as well, makes sense at least to me. So we'll go like this, anyhow::Error. So we're referring to this thing right off the package as this is the error type, the anyhow::Error, which means we can also use the anyhow::Result.

So what I'm gonna do is I'm gonna go use anyhow. For whatever reason, it always has a really hard time dealing autocompletion and just grabbing it from the LSP using result cuz there's already a result object globally. So I'll just go grab the anyhow::Result, jump down here, and simply return that.

There we go. So now we're using the anyhow::Result, and just in case if you don't believe me, you can always jump to definition. And where are we at? We are actually in the Rust lib. That is sad, that's not what I wanted to see. But that's okay. It's probably still using the correct thing anyhow, just decorates it, and so probably it still works out just fine.

All right, anyways, so there we go. So now we have a from string. It's gonna give us a reference to a string, meaning, it doesn't consume the string, it just simply needs a reference to a string. And then I need to produce out a result of self. Self is just a nice way of saying, what is the info on?

So it's actually point. People use this a lot as a way to do that because that way, if you make slight changes, self still just universally works. All right, so once I take away the todo, it's gonna say, hey, you're returning nothing, but I expect a result. So let's start doing that.

So if you remember from last time, we did have a and b, right? So we could split once on this string. And there we go. So we have this once. That's how it's split, but this thing doesn't return an error, unfortunately, it returns an option. So we have to handle the option case.

If there is nothing, what should we do? To me, kinda seems like we should probably almost return an error like, hey, this is a bad line. We should have handled this better. So let's just do that now. So I'm gonna go like this, let result, I can just do that and go if result.is_none.

I can just do something as simple as anyhow!("expected a point to contain a comma"), right? Beautiful, and of course, it wants Err because it is a result. And we have to import this anyhow macro. So you should be able to just simply use, I can use a code action for it.

So it's located right here. So you can just change your import statement to look like this. If your LSP was behaving, which mine is not, it should have just auto done that all for you. A year of them, I'm telling you, it's gonna work out. So there we go, we've handled this case.

We can now safely unwrap it cuz we do know that it does exist here. We could also have done an if statement right here and just pulled it out via pattern matching. We could have done a couple different approaches. Let's just do this one. It's the simplest form of it.

So I'll go (x, y) = result.unwrap. The nice part about doing this, though, is that if we used expect, it would have hard crashed our program if there's an error. Instead, we're bubbling up some error with a little bit of context on it, should be kinda nice, just kinda get us a little bit more familiar with error handling.

There we go. So we have these nice x's and y's, which of course, are references to string because we just split once on a reference to a string, so what comes out? Reference to a string. It also makes sense if you had a capitalized string, it would still be a reference because it doesn't need to create copies.

It just needs to refer to these two little points in time. And so you can kind of guess what a reference to a string is. It's just a point in the memory and the length, right? It just can tell you how much there is. And so there we go.

So now that we have that, we now take x and we need to turn it into a number. So we can do that pretty easily by doing, well, let's do i32. Is that what we did up here? Yeah, we did i32, it's awesome. We can do i32 and we can actually do a str::parse, and we can provide x.

Now, remember, since we have the anyhow library, we should be able to do .context, and it's not showing up. Isn't that what this thing returns? Yeah, it should return, okay, we can at least do that. I was hoping to put a little context on it, but I mean, do we really need context?

Do we really need context? It's probably just not showing up. I've been having a little bit of problems here. I probably just add in the word context, all right? And now I should be able to go context. Something tells me that my LSP is struggling. We'll just continue on this later.

There we go. And now we can return a Ok, a point, and of course, x and y. Remember, we do not have to specify them in the proper order because Rust will break it down into variables matching named properties. So there we go. We have now done this.

We have now created a from string for point. Here's the error it emits, here's how you do it. Let's just do that again, but let's do it for line, right? So now notice that I just changed line and I don't have to change anything about this statement. We use that self, so this is another reason why self can kinda be useful as you do things.

Self just makes a little bit easier to work with this. There we go. So now we're just gonna simply split once on this. Now remember, this will give us out two points. We can do the exact same thing right here, expected a line to contain this, why not?

This is easy, right? We'll unwrap it again. We'll just do all the same things. And now we can just string one. We can just parse this thing out as a p1 and as a point. And look at that, it works. We were able to just simply call str::parse.

We gave our variable a type and it knew it. And Rust is really good at understanding types. So we can even not give it a type, like that. Increment this, increment this, and then do a simple line and go p1, p2. And it's going to be able to infer if lines p1 is of type point.

So therefore, when I call str::parse, it's gonna need to return out a point, and the point's implementation is right here. So it actually does a lot of inference pretty well. I actually really like this part. It's because, especially if you come from a C++ background, there's just so much type specification constantly, and then to get around, they use auto.

But auto just doesn't quite give you that same good amount of information, whereas this, I can see right away. It correctly identified exactly what that type was because of this return statement. Now if I didn't have that return statement, and instead, it did, say, return Ok(Line) and I'm not sure if I can do that, p1 0, 0, can I just use that?

No, I have to do that. Okay, we could show you, but whatever, it's too much work at this point. Let's just move forward. If it doesn't have the type, if it cannot infer, then it's gonna give you that nice hard error. But if it does have that information, it will gladly do it for you.

So there we go, we have everything. I think we need to be able to solve this problem, except for one thing. How do we know if the line is horizontal slash vertical? So we can do one of two methods. We can either have a function that is kinda free floating like we did in TypeScript is, everyone's favorite word, horv, is horizontal or vertical, or we can attach it to the line.

Which one, since we did it the other way, and you guys are unfamiliar with Rust, let's attach it to the line. Even though it probably is not as convenient as simply just having it as a free floating function cuz then I can just pass this into filter and it'll do the right thing.

So instead, let's just attach it to the line. So I'm gonna go impl. Remember, impl is how we say, hey, these are implementations that exist on this struct. impl Line, and now I can go fn is_horv. And of course, the only thing it takes a reference to is a non-mutable version of itself.

So if you're wondering what this means right here, this just means, hey, I need to be on an instance and I cannot mutate the data. So if you wanted to mutate the data, you could do something like this, right? You could do a mutable reference. Now this would require that your variable is mutable when you create it.

Or you can have something that's more like a static member. So if I had the exact same thing, I could take in a (line: Line), right, a reference to a line and return out a Boolean. And each one of these would be able to be used. Here, let's just get rid of this one cuz we don't need that one.

We'd be able to do something along the lines of Line::is this. And then I have to pass in the line because I'm not doing a self right here. Or if I use the self, well, then I'd have to have a line object in which I call it on.

So that's kinda the two differences, but that makes sense. That's kind of just like how it is on a class on TypeScript. Exact same constructs exist. So let's make sure if it's horizontal or vertical. So let's return self.p1.x == self.p2.x, or copy paste, erase, erase, move. My goodness, I forgot where x is, x/y, there we go.

That's why it wasn't working. I was like, where is it going? All right, there we go. So long as point 1x equals point 2x or point 1y equals point 2y, we have ourselves a horizontal or vertical line. And of course, if we exported this thing, we would need to make this thing public.

We're not exporting it, we're just using it right here. Awesome, there we go, we've done it. Another thing to take notice is that Rust is really great at identifying things that aren't used. So when it says this, this is actually unused, you can pretty much rely on this 100% of the time.

Sometimes it's a little dangerous to use static analysis tools that erase code cuz not all code is actually not used, and then your life is very, very sad. And so we don't have to worry about that. So we have everything we need. We have the input, we have our point, we have our line, we have our from string, we have our line method is_horv.

And so let's actually use this. So I'm gonna go get_input. Now this is the fun part. We're gonna do this again. Let's use all the fun iterator stuff. So we can call lines on it, you remember that? And so now we need to take our string line and convert it into a line, right, a line struct.

So can anyone out here right now kinda take a guess, if you're following along, what should be the next method I should call on this nice beautiful iterator?
>> Map.
>> Map, so check this out. So let's call map, and let's take an x, right, x is just, as you can see, it's just gonna be a reference to a string, cuz we haven't done anything yet.

And let's go str::parse(x). All right, so what do you think the next, oop, I realized that Rust hates it this way, never do this way. Remember, Rust prefers it this way always. All right, what do you think the next value of x? What do you think the type is gonna be on this one?

Anyone wanna take a guess what it could be?
>> Point.
>> Well, right now it's unknown because it doesn't know, but it does know one thing, it is a result object. So that means it still potentially contains an error. Now, remember earlier, I did tell you about that result in options have that .map method.

Remember, what does a flat_map do? Well, flat_map takes in something that can be mapped, and then pretty much flattens it, kinda like array.concat, will bring it into the same iterator. So every subvalue will be pumped out. So I can actually go like this, flat_map. And it will only on the results that aren't errors continue on.

So that's pretty cool. So now if I look at the value of x, it's now just an unknown. It doesn't know what it is quite yet cuz we haven't given it enough type information, but it does know it's no longer a result object. That's actually pretty cool, right?

That makes handling errors super slick. If you just don't care about the errors, just ignore them, flat_map them. We're ready to rock. I love that, fantastic. So let's call a nice filter method. And of course, we're gonna go like this, is_horv. It doesn't know it quite yet because it still hasn't quite figured out what it's supposed to be at this point.

And so at that point, we can then call a collect, if we want to, which a collect will take something and turn it into a vector or a string of something. And so we wanna turn this thing into a Vec of Line. And so when I do that, it now should have everything.

And this one still complaining for it, so we can give it a type, which it'll be a reference to a line, right? There we go. It's a reference to a line. It now knows its type. Everything is good, and we can go lines right here. And everything is beautiful, quick println.

And of course, put in one, remember the man smoking the pipe. Now, what are we gonna do here? We have the same error, does anyone remember, what do we need to attach to a struct to make this successful?
>> The macro thing.
>> Boom, the procedural macro, the proc macro.

Actually, I might be wrong. I assume it stands for proc, procedural. I mean, I guess it could stand for proctologist, but that'd be weird. derive(Debug), there we go, take that. But even though I do that here, I still need to do it here, right? Cuz the line doesn't know how to debug itself.

Same thing this way, it would go, hey, you are doing debug implementation, but your subcomponents cannot be debugged, therefore, you cannot implement this like that. So I'd have to do this. There we go, we now have everything. And of course, it prints out just fine. We can do a nice cargo run --bin p2, and we should see these beautiful nice lines.

Again, these are all our horizontal or straight lines. You can see yy, it's yy again, yy, then it's xx, xx, then it's xx again, 77, then it's yy for the last, yy, yy, perfect. Look at that. We only got horizontal or straight lines. And so that's all we have to do.

So that's pretty nice, right? Once we've created these nice implementation details on the structs, working with them in a more standardized way becomes very easy, right? This was very, very nice. I just have to call str::parse, just parse the damn thing, right? I don't have to know about it.

Now I think I might be wrong on this one, so let's just try it out. It's always good to be wrong. I believe you can just do this, too. I'm just gonna pass in this function and it's gonna know what to do. Because it can know what to do because someone wrote some very magical compiler magic out there.

And there we go, right? Because str::parse obviously takes a single argument. This thing gives a single argument. It is parsable. There we go, and so that is problem two.

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