About this video
What You'll Learn
- Create a new Rust Cargo project and wire a first CLI app with main, tests, and return types.
- Apply lifetimes, impl Into, and where clauses to define flexible generic anagram function signatures.
- Build command-line parsing and file processing with std::env args plus BufReader iterators to find anagrams from a dictionary.
Jane Lusby walks David through writing a CLI anagram finder in Rust, covering cargo project setup, editions, lifetimes, generics with impl Into and where clauses, command-line parsing with structopt, and reading the system dictionary with BufReader and iterators.
Jump to a chapter
- 0:00 Holding screen
- 0:30 Introductions
- 1:06 Introduction & Guest
- 4:21 Episode Plan
- 4:58 Hello World: Project Setup & Basic Output (Cargo new, lib/main)
- 5:00 Creating our first Rust application
- 7:56 Understanding Rust Editions
- 8:00 Explaining Rust editions
- 8:30 Creating our first Rust application
- 9:16 Main Function, Tests, and Basic Return Types
- 13:41 Adding Parameters & Initial Testing
- 15:45 Lifetime elision
- 16:21 Understanding Lifetimes (`'static`, `'a`)
- 19:00 Generic types and functions with Impl Into Option
- 19:54 Handling Optional Inputs & Exploring Generics Syntax (`impl Into`, `<T: Trait>`, `where`)
- 27:44 Rust's Lack of Function Overloading Discussion
- 33:40 Program inputs with std::env
- 33:48 Getting Command Line Arguments (`std::env::args`)
- 36:00 Lifetimes
- 41:00 Writing an anagram finder
- 41:14 Anagram Finder Project Setup (Cargo new)
- 42:17 Defining Anagram Function Signature & Initial Tests
- 51:58 Dependency Management: crates.io & cargo-edit
- 52:50 Pulling dependencies from Crates.io
- 55:00 Looking at Structopt
- 55:01 Command Line Parsing with structopt (Demo using Hello World logic)
- 1:04:35 File I/O: Loading the Dictionary (`BufReader`, `lines()`)
- 1:05:00 Loading a file with BufReader
- 1:08:40 Handling File Open Results (`if let`)
- 1:14:36 Converting Iterator to Collection (`collect()`)
- 1:15:20 Collect
- 1:16:06 Anagram Logic: Iterators, Filtering & Sorting
- 1:17:00 Iterators, Filters, and Map
- 1:27:08 Iterator vs IntoIterator Distinction
- 1:29:12 Testing & Debugging Anagram Finder Logic
- 1:30:30 Helpful Resource: cheats.rs (Language Sugar)
- 1:32:17 Conclusion
Full transcript
Generated from the English captions. Timestamps jump the player to that moment.
Read the full transcript
1:06 Introduction & Guest
1:06 Hello, and welcome to another episode of Rawkode live. I'm your host, Rawkode. Today, we're gonna be continuing our exploration into the Rust programming language. Now before we do that, I just wanna take ten seconds to say thank you to my employer. I work for a company called Equinix Metal, and we are a bare metal cloud company. If you wanna check out Equinix Metal, there is $50 coupon for anyone that wants it. It is Rawkode dash live. And that's actually around one hundred hours of bare metal compute time. So please enjoy. Today, I am joined by
1:37 Jane Leslie. Hello, Jane. How are you? I'm good. So you are the organizer of the awesome Rust Mentor Project and the lead of the Rust Error Handling Working Group. Yeah. Do you want to give us a little bit more information on you? And then maybe we'll talk about those two projects as well. Sure. Yeah. I've been programming with Rust for, like, two it's two or three years now. I think it's been almost three years now. And I, you know, I got real into error handling because of, like, this library that I wrote that I want to open source. And I
2:14 was I was kind of unhappy with the error handling approach and just tried to fix it and ended up realizing that it was, like, ecosystem wide problems. And now I'm, like, you know, leading the project group, trying to fix the annoyances that that still haunt that project. And so, yeah, I'm really into Rust and excited to help out. Awesome. And what about the awesome Rust mentors project? Do wanna tell us a little bit about that? Yeah. So that's just it's like a list of people who are interested in mentoring anyone who's interested in learning Rust.
2:49 So there's like it's just got a bunch of people, their contact info, kind of like the domains of Rust that they're interested in, like, they're interested in, like, async a wait, CLI applications, etcetera etcetera, and maybe some, like, background resources, so, like, links to their blog and whatnot. If you're, like, learning Rust and you, like, just wanna kinda, like, get connected with the community, I think it's a really good way to, like, get to know people, especially, like, some of the friendlier people in the community are I think are I may be biased, but I think these are the, you know,
3:17 the the good people. And a lot of them are obviously or my friends, probably unsurprisingly. Though I do have lots of friends across the Rust community because, like, you know, that's where I hang out nowadays. So Yeah. I've gotta say as someone relatively new, the Rust community has been extremely welcoming and very helpful with all their content and the conferences and the talks and example repositories. Like, it's just a very welcoming place to be. Yes. For anyone who's not familiar, you can find the awesome Rust mentors project on GitHub. And it's got a list of of mentors.
3:48 So if you are familiar with Rust and competent with Rust, feel free to send a pull request and get yourself added to this and help people out. Yes. Yes. We could always use more mentors. So please. Awesome. So we have a couple of comments already. Woo, Jane. There you go. We got another woo hoo with a little crab and we got some good vibes coming from cute daily. So thank you. If you have any comments or questions as we kind of work along our exercise today, just drop them in the chat and we'll do our best
4:19 to handle them as well. So today's plan is a big one. Now we have already had two episodes. My dog is going crazy. I don't know if you can hear that. There we go. So we've already had two episodes and we really took a look at the syntax of Rust and explored, you know, string types, numeric types. We take a little bit of look at struts, a little bit of error handling. But we haven't actually written any Rust to actually compile as a real application. And that's what we're gonna explore today. And we have a couple of potential sample
4:21 Episode Plan
4:53 applications that we're gonna try and work through to giving on time constraints and how quickly we move. But we have to start with other worlds. I think that's everybody's foray into a new language, so we just do that. And really, it's not just about the print line statement of the whole world, but the cargo command to create the project, etcetera, and the tooling that we use to build that. The second idea will be that we try to work with an anagram finder. I have a very large file in my directory here with all the English words in it. So
5:00 Creating our first Rust application
5:21 we'll see if we can write something that can find potential anagram information. And then a very contrived calculator which will allow us to use English input as well as numeric input. And hopefully, these three examples will just allow us to explore some of the idiomatic Rust things that we need to learn and the tooling to be able to work with Rust on a daily basis. You happy with that? Yes. Sounds good. Alright. So as far as the hello world goes, this is what everyone usually does when they when they wanna learn a new programming language. So,
5:53 I mean, I could just create a Rust file, inject the code, and use Rusty or Cargo Run or one of these things. But if I was going to do this as a real application, what would be the first step for you as a as a Rust developer? For me, it's just like, you know, you immediately go Cargo new and dash dash lib. I I pretty much always do lib because I like to when I'm writing even if I'm writing a binary, I like to split it into two so I can do doc tests and stuff.
6:22 And then just whatever you're gonna name the project. So and it's it cheats because the the default generation is literally a hello world program, so you're done. So you hit enter and you're done. Alright. I'm curious about that lip thing then that you mentioned. Mhmm. What does that what do we do when we tell cargo that dash dash lib? What's different from maybe a normal application? So with dash dash lib, it changes slightly the, like, the the project it generates. It generates a lib dot r s instead of a main dot r s. And it
7:01 doesn't I think it specifically generates a slightly different gitignore file because with, like, with the bins, you want to usually commit the cargo dot lock file. But with libs, you generally don't want to commit the cargo dot lock file. So, actually, we should probably delete that. That's a good point because we are actually creating a binary. Okay. So we do wanna commit the cargo dot lock. But other than that, it's it's pretty much generating the same files as far as I know. Like, it's gonna create a cargo dot toml, has, you know, whatever name you
7:35 gave it and just the defaults whatever is, like, the newest stuff. So it always put 02/2018 edition for a new project now. And then probably in two years, it'll say 02/2021. And I think that is it. I guess it does generate a target directory, which is fine. Surprising, but not weird. So with regards to this edition, and that's not actually something I think I've asked any of the previous guests. But what is significant about the 2018? So one of the the cool things that Rust does is we we allow breaking changes in the language, and we do so in kind of a
8:00 Explaining Rust editions
8:15 structured manner that allows you to, like, interoperate between different edition, like like, libraries written on different editions with the same compiler. And so when you enable the 02/2018 edition, it's essentially a set of features that are not backwards compatible. Like, a lot of features and a lot of different, like, settings on, like, which warnings are, like, errors versus warnings become activated when you turn on this edition equals twenty eighteen. And so, like, the compiler has, like, all of these, like, switching logic on, like, if it's if you're currently compiling a twenty eighteen crate, you're gonna use these rules. And if
8:30 Creating our first Rust application
8:50 you're not, you're gonna be, like, twenty fifteen and using these rules, and soon there'll be a twenty twenty one, which have a whole new set of rules added on. There are some restrictions, like, the standard library is not allowed to do breaking changes because it's linked to both versions. So it has to be API compatible with two with old code and new code. But the language itself is allowed to, like, introduce new keywords and everything. Alright. Awesome. Alright. So let's take a look at our lepto r s end. It looks like we've just got some test
9:16 Main Function, Tests, and Basic Return Types
9:23 test configuration. Yes. So I could delete all that? Yeah. I mean, it's I I think it's, like, maybe it's it's okay to leave it or to delete it whether or not it depends on if you're plant you're gonna, like, write lots of little tests while you're going, which is kinda, you know, on dev style type thing. If you prefer to just, like, do lots of testing or upfront or you prefer to just, like, kind of test it in the by running it, you know. Yeah. Maybe we should I was just gonna say maybe we should write
9:53 a small test or under her little world application, you know, just to to explore that concept. So does Rust use a main function, like like go? Yes. It's main and then the the signature is basically no arguments and then it returns a termination type. Termination is a trait that just says, like, I can give you an exit code. And right now, it's unstable, so you can't implement termination on your own types without going to the nightly compiler, and I think, like, maybe enabling some features. But there are a couple of types that are stable that do implement termination. So you
10:31 can use results and you can use a unit type, is, like, no return type. It's implicitly a unit. And both of those will, like, do the, like, error reporting if there's an error or just, like, exit status or whatever. Okay. So to to make sure I understood that, this is what you mean by the unit or no return type and that implements that termination tree? Yes. And so if you leave this as is, it will compile because that is type checking because, like, you could change main to put, like, a return type and have that
11:01 be e like, returns unit. And, like, this is equivalent. Like, I think that, like, Rust syntax sugar is a way of that. But, like, functionally, it's as if it were there all the time. Whenever you don't have a return type on a function, it's returning unit. Oh, okay. And then we can do print. Oh, hello. Will this compile? Yeah. I believe so. It should compile. Well, it won't compile because it's in a it's a a lib dot r s file. So you're gonna need to make a new main dot r s. Or it I mean,
11:38 it would compile because you just have hello world colon colon main as like a library function. It just wouldn't run because it's not it's not a pin. So you need to, like, make a main file. Alright. Alright. Okay. But we have a main to r s as well and this is a special file. Yes. And so then if you put an f n main here, it's going to be much happier. And then you could, for example, you could just call hello underscore world colon colon main. That? And yeah. And that this should compile and run.
12:22 Why is it upset? Oh, this is not yeah. Yeah. Okay. So that just means I had help. Right? Exactly. If I leave the semicolon off, that's what count as an expression and it would return whatever this function returns. And it's that's a unit and it would still work. Yes. Okay. So I know this is a contrived or simple hello world application, but let's assume this application is gonna make us a million dollars. Like, how do I build it? How do I run it? And how do I do the tests? What will we do? So cargo build is
13:01 high oh, so you can do cargo check. Let's start with cargo check. So cargo check is essentially checking that it compiles, but not generating any artifacts. So it does all the type checking, but not any of, like, the I think, linking or whatever. And then cargo build is what actually makes your artifacts. And so this is, you know, obviously, it's gonna take a bit longer. And then cargo run is what runs the main. And so this should give us hello world, and then cargo test is how you would run the tests. Nice. All pretty self explanatory there.
13:40 Okay. Let's make this a little bit more interesting then. So what if I want to be able to tweak this to take a name and pass that n instead of world? Okay. So you would there's a lot of ways you could do it. So let's start with the simplest one and let's just use a string. So just let's say name and then colon and then the type which would be ampersand s t r. And then in this one, you're gonna just replace that with a formatting specifier, I think, is what it's called technically. Yes. And then
13:41 Adding Parameters & Initial Testing
14:24 comma and name. And that will do, you know, your whole string interpolation print f style thingy and inserts that and runs the display trait and gets that done. Of course. And now this takes an argument. So I mean, I could just cheat and pass this in like this for now. Right? Yes. Okay. So if I run do I need to always do a build or cargo or cargo run did that for me? Cargo run will always do the the the build as a prerequisite. Yes. Okay. So let's I'm gonna just make this as as contrived as possible now. So
15:04 I'm gonna write a test case here. So is is that at is that mandatory or can I just put anything here? You can put any any test name. Yeah. Any name. It'll just be the name of the test. Yeah. Okay. And then if I I'm gonna break all my code now completely. But let's say I want to call main with no parameters and then assert that I get back hello world. I know we'll fail right now. So this shouldn't compile. Because of the the type mismatch here? Or Yes. Exactly. Yeah. It's it's expecting a an argument and it's
15:45 Lifetime elision
15:51 not getting any. Yeah. I'm I'm gonna pretend we never made that change for a moment. So Okay. Because I had my alright. Okay. And so also, this is search. Well, it's still not compile because the the print line is a side effect, not a return type. Yeah. Okay. K. Oh, it doesn't like that. Yes. And so that won't compile because so when you put the str as an argument, it gets some there's some syntax sugar applied. It's called the lifetime elision rules. And this can still apply in return arguments, but it there there are, like, certain rules
16:21 Understanding Lifetimes (`'static`, `'a`)
16:36 that have to be satisfied. Specifically, you need to have, like, a a self argument in order to in order to align lifetimes in the return arguments. Or it might be if you only have one argument. It might I think it might align the lifetime in that case too. But because you have no arg input arguments, it's go it doesn't have anything to get the lifetime from of this string. This this borrow is like this reference has to be constrained to some, like, dirt this, like, some scope in the program, essentially, where, it thinks that that that string would
17:11 be valid and or that reference would be valid. And so in this case, because we're not actually borrowing any existing reference, the thing we're trying to say is, like, this is just a static reference. It's a reference to, like, a piece of data that is just linked into a specific part part of the binary, and it just lives there forever. It exists for the duration of the program. And so we have to manually write in tick static after the ampersand, and that just says, you know, this is a string that always exists. And then it won't have to, you know,
17:44 be derived from some input lifetime, which is what you normally do. Like, lifetime parameters are always just, like, relations. Like, is this something that comes from this other thing? Okay. That's a really good explanation. I have I have not really understood that previously before and that kinda made a lot more sense to me, so thanks. No. I'm thinking because we're gonna be allowing this world to be interpolated later, should we just go ahead and make this uppercase string? Or we just don't put that as an uppercase string? So we're not gonna be able yeah. You're
18:20 right. We're not gonna be able to add anything in to the string if we use a static string because, like, it's a static piece of memory that we're not supposed to modify. And so yes. Oh, so They're happy? No. It's not happy. Expecting a function Super. Oh. Oh, yeah. Because this is a the one module. Yes. Yeah. Okay. Superman. That's no happier. Now let's remove this. So this doesn't take a parameter anymore, but it's actually not gonna do anything. So let's just make this past roughly to where we had it. And then we can do a return.
19:00 Generic types and functions with Impl Into Option
19:10 And you're probably gonna need a just an empty format string that has like a single Alright. Cool. Okay. Now let's go back and see if our cargo run works, which it does. And our cargo test, which should. And now we wanna make this our $2,000,000 application, which means parameter. I I still really struggle with when to use an uppercase string and when to use this as ampersand string. If we're gonna be reading m poo and the previous function, would that mean I pass it through as an uppercase string because it's dynamic? Is that like a
19:51 good heuristic? The the question is if you're gonna modify the the piece of data that, like, is the input string. So in this case, we could do that. We could, like, basically, like, expect them to give us a string and then, like, they are giving us the name and we're going to prepend hello on it. And that would work, so you could. I think it makes a bit more sense to expect just a a stir because then we can, like, we can make our own starting string and then we can just use the plus operator, which can catenate strings,
19:54 Handling Optional Inputs & Exploring Generics Syntax (`impl Into`, `<T: Trait>`, `where`)
20:30 and it'll be a bit simpler. Okay. Cool. So assuming we were doing TDD and we were doing this, the test case first. So we've got users world as default, but we also want to accept first accept the name, means we're gonna do pivot, and we expect pivot here. And we're in a predicament now where we need to handle both of these cases where we have an a parameter and we don't have a parameter. What is the idiomatic Rust way of handling that? So this is one this is one of those things where Rust isn't, like,
21:16 super opinionated, I would say. And so there's a lot of ways to handle, like, overloads, essentially. And you can like, you could just have multiple functions. You could have, like, multiple main like, main and then main with arg or something is one way to do it. You could define a trait that has an associated type, which is, like, the input and then, like, do some type system shenanigans, but that's probably, like, way way too much. And I'm not even sure you could get, like, a nice to use API out of that in this case. In this I I would recommend for the
21:55 for just this simple example where it's just, like, a single argument to do an into an input into option. So what you can do is instead of ampersand str, right, you do input into and then inside of the per type parameter for into, it's option ampersand stir. And so what that's gonna let us do is either pass an option or pass just the stir itself. And so it'll kinda get so you would have to change the interface on, like, number nine, like, line nine. Just put a none inside of the main. But other than that, we can make this
22:40 work where you're passing none or you're passing in David. It's kinda like that's the way that I like to do this exam exact example. And so instead of option, it's impull into option. Like let me see if I can type. Oh, I cannot type. Like that? The into is a is, like, around the option. Like, it's, like, capital I into and then open angle bracket and then close angle bracket. Yes. Does this not like the what's this saying on the the the ampersand? Lifetime. Really? Sure. Yeah. Oh, that's it. Okay. I have no idea. And so the way this so so
23:32 oh, you've not you've not been introduced? Okay. So in Rust, there are three ways to specify generic types in functions. So let let's do main two and main three, just, like, copy this function, and we'll do all three versions. That's cool. Okay. So the first one is the is the into option. Now change the input into in both of these to just t. To just what? Sorry? To just t. Like, just the capital t. Like, the entire type should just be t. Like, no option, no ampersands there. Just And do that on the second one as
24:19 well. And then on both of those, main two and main three, we have to add a lifetime parameter. So, basically, we have to declare the type that is generic. Right? So it's like, before the open parenthesis, you're gonna do an open angle bracket, capital t, close angle bracket. No. No. No. Out right after the function name. And then in the first one, we're gonna do a a colon directly after the t inside of the angle bracket. In in the no. In the in the type declaration, in the, like, the type parameter. Yeah. And then we're gonna put the same into option
25:06 ampersands turn, but we're not gonna put impl. And so this is saying we are declaring a generic type, which is basically it's like a variable for types. Right? It's like if if, you know, your name is a variable for strings at runtime, t is a variable for types that compile time where it could be a string or it could be an option string, etcetera, etcetera. And then in the the third one, in main three, we're gonna go after the return type, and we're gonna put a new line. And we're gonna type where and then new line
25:43 and then t colon and then the same thing. There you go. So all three of these are the same thing. There is one very slight difference in that you cannot manually specify the type of the first one. The first one is like a is like a a more restrictive anonymous type parameter, so you can't do, like, turbo fish. So, like, normally, in in main, you could type, like, super main colon colon open angle bracket and then the actual type, and then it would type check. It would not infer the type. But with the the first one, because it's an
26:35 impl, that's disabled. But the second two, because they are actually declared in the the type parameter list, then you can actually turbo fish and specify what you think the type should be. But in all these cases, we're saying there is going to be like, we accept multiple different types of types. So it's gonna it's gonna generate a compile time multiple versions of this function that accepts different types. And as long as it has an into option, it'll work. And an into option star, it will work. And right now, all these like, we're not actually like, the
27:06 tests are gonna fail because we haven't actually started using that type parameter correctly yet, but, like, we can then, like, implement like, to call the into trait to get an option, and then we can check it option, see if we have a name, and then handle the default case and do everything that our tests kinda expect us to do. Okay. Awesome. So just to make sure I understood this correctly. These are two different approaches for providing a function that takes a generic type when they were given a little bit of guidance to what we think that generic type is gonna
27:35 be. I'm assuming with this one, can I have multiple implementations of this where the WER type is different without changing the name? No. Yeah. Rust doesn't do overloads like that. That that's like what you're what you're talking about is called specialization. And I'm not sure specialization will be usable directly on functions. I think it it'll have to be done via traits. I'm not sure. It's like the feature doesn't exist yet. So it's like Okay. I'm I'm just thinking, I guess. Let this list to me. I had a lot of a Alexa experience. This feels like a guard
27:44 Rust's Lack of Function Overloading Discussion
28:13 where we're saying we expect this type of function to be exist here. But I would normally expect that to be over loadable, but It's not. That that's fine. So let's comment this one out just now. This one, I understand. You explained that very well. And I wanna do one more thing with this one before we move on. Who'd have thought we'd spend so much time on Hello World? Like but it's it's really a lot of noise coming from here, so I really appreciate that. Now I wanna talk about the semantic differences between these. Now my understanding of Rust, I think,
28:47 is this this this is just an online type where we're saying that something that implements the n two trait, so it can automatically do the conversion for us. Mhmm. Whereas if we do this option, we would expect the option to be passed in, meaning we would call some David. Mean Exactly. Is that right? Okay. Yes. Cool. Yeah. I I've never seen this in line implementation of a tree kind of type parameter. That's pretty nifty. Yeah. It comes in handy sometimes. Like, I I I tend to prefer wearer bounds as like a rule just because, like, I
29:24 think they look a lot nicer and they're easier to read. But especially for, like, really short traits, I find it can be really nice and definitely and even more readable than whereabouts. Just do, like, import as ref u h or something like that, you know. And if you're just it's just gonna be, like, very short, like, 10 characters or less trait name. It fits well into a type position. Alright. Awesome. Okay. I'll leave that comment today because when I push this code up and added to the show notes, I think those examples might be useful for for other people.
29:56 So let's make our test pass and then we'll move on to the next example. So right now, this is still gonna fail, but we could well, let's see what happens when it fails. Your main has to put none in it. Of course. Okay. Also, my app my application didn't compile, so the test didn't run? Mhmm. Is that what alright. Okay. So the default work, we don't have the names. And then we update our code, like, good TDD developers, and we say, okay. This is the name. And that won't work. But yeah. So that's, like, the first step. Right?
30:43 And so the reason that won't work is because right now, the only thing the compiler knows about that type is that it implements the into trait. It does not know if you can display it. It does not know, like, if it is already a string or an option string. Like, it's less so it will not let you do anything except call into. It's literally the only thing you can do with this type. And so first, you have to like, before you I would do this on, like, a line before that so you don't get, like, some, like,
31:12 super long thing. Just do, like, let name equal name dot into. And this is just gonna first invoke that trait that we constrained the type by. And so that now we know for sure that name is an option static string. Right? And so you can print that, and it will probably still not compile because I don't think option implements display. I think it only implements debug. And we could unwrap it, but that would not that would break the default case. But but that might be, like, a fun, you know, half next step. Okay. So I understood that correctly. That is just
31:51 con that's just confirms that we have an option string types. And we It does the conversion and gives us the the the type that is now, like, statically known. K. So would that work? No. It would work. Yeah. That would work. But your main would still probably not compile. No. Your main would compile. So why are you complaining at me, Rust? I don't know why the error is at the bottom of the docs and not the top. I keep meaning to fail an issue for that. It takes one parameter, but two were given. Oh. Oh. I see the string from. Yes.
32:33 So you just change it to a format. Not not string format. Just Yeah. For yeah. There we go. Yeah. K. So it should compile. Doesn't implement display. But you don't you you're not using display. You're using debug. Oh, it just took a moment. Oh, okay. Yeah. So that should compile, but should fail tests. Yes. Because we have okay. So now we have the option type. Yes. And so now we just wanna do is unwrap it and provide the default case. So we're gonna do unwrap or unwrap underscore or and then world. And so that's gonna give us I think
33:25 it needs to be lower case based on, like, your Yeah. Yeah. And so that'll either give us the string that we were passed in via the into type, or it will give us the default case. And that should pass through your tests. Yay. Nice. Okay. Cool. So the next step would be if we want to accept and What kind of input you wanna accept? Why don't we like, I don't feel we need to go down the route of prompting interactively. So if if we could just make it satisfy something like cargo run with the first
33:48 Getting Command Line Arguments (`std::env::args`)
34:07 parameter and we'll just pull that out, pass it through, and then Okay. So the the easiest way to get the the parameters is from stood and it's one of the modules in this shared library, and it provides the args functions. So args, the the fourth one in that list, gives you an iterator type, which is gonna iterate over all the args in the command line. And the the first one should be the program names. You're gonna want the second one. So, like, dot nth one with just n t h. Oh, nth. And then, like, let yeah. Let me pull
35:00 that. And then you could probably just pass that straight in and because that's that's an option as an into option, so that works. And that'll just if you don't give it an argument, it will do hello world. And if you give it an argument, it'll do hello whoever. But we get an uppercase string instead of Oh, okay. So then do as ref after name. As ref. Oh, what do we got now? Yes. I think we As d ref? Does not live long enough. Yeah. It says name does not, like, live long enough borrowed value. Borrowed
35:50 value does not live long enough. There we go. Oh, because because because we constrained it to a static type. So can you go back to the live dot r s? And can you just instead of static, can you can you add a just do tick a and then add a a generic parameter for just the lifetime? So go back to the main like, the function name and open angle brackets and then tick a. And let's see if that's happy. Yeah. There we go. We're good. I don't know why it's not able to infer a lifetime inside
36:00 Lifetimes
36:26 of the trait the into trait. That's that's strange. Must be Okay. So let's break that down as well briefly then. So when we use the tech static, what we're saying is this string will live in memory for the lifetime of the program's execution. Yes. When we say tech a, and this is part of the signature here in this function, what we're saying is the string will live long enough for this function to run only. Is that no? No. No. What we're saying is the string will live for some generic lifetime. So tick a is a generic parameter that
37:00 is like it it's acting like generic, though it's like not really the same thing because it has, like, all sort of special case rules because lifetimes are wild. But, essentially, it's saying, like, there is there is some lifetime that, you know, that this function will accept or, like, this will accept, like, any lifetime, really. And the this is specifying, you know, whatever that specific lifetime is. Like, it's it's not the lifetime of this function. It's the lifetime of the data that is being passed in. It is, like, actually, when you pass in the data, it infers
37:34 the lifetime that that variable is associated with from the data that it's passed in. So now for that, like, specific piece of data that you passed in, it's like that's what the tick a means. It's like the string that you're passing in in the main function is the tick a is referring to how long that string lives. And the the entire like, how you use that throughout the rest of the function definition is basically constraining, like, oh, however long that input string lives, you have to make sure, like, my output type. If I were to, like, put tick
38:00 a as like constraint on the like the borrow of like a return type, then like just make sure that like whatever I returned to you doesn't live longer than that borrow that you made on this string in the main function is what you're essentially saying. Awesome. Cool. So the compiler works out then. Right? Yes. Okay. We have a couple more comments there. So Patrick says, never consider using that interesting. That's nifty. Yep. I I thought the same. But also followed up with, could we use a tick underscore for the lifetime instead of tick a? Yeah. Let's try it. Get get
38:36 rid of the the parameter, the declaration and no. No. You did just put nothing there. Like, get rid of the the angle brackets altogether and just put tick underscore. And see if that works. No. I can't Can you run it in the terminal? I just wanna see if it gives I think it might be cutting off some of the diagnostic. Don't Sure. Missing license is fire. Okay. It does not specifically say that you're not allowed to do interesting. Okay. Go back. Yeah. I was just I was expecting the diagnostic to be, like, more aware of the situation. Because a lot
39:17 of times, like, Rust actually understand like, the compiler understands a lot more than just the language. This is, like, if you ever watch Esteban's talk at RustConf, he he goes into this. It's it's quite good talk. Highly recommend it. Where, like, you have to understand, like, a superset of the language in order to, like, do good error messages. You have to understand what people meant, not what they said. Even if what they, like, said is completely nonsensical in your language. And so I would have expected in this case for it to be like, no. You're
39:47 not allowed to put an inferred lifetime here because of this rule and to give you, like, a clear explanation, but it didn't. So I was that's what I was a little bit surprised by. Oh, okay. Cool. Yeah. So one of the previous guests was talking about the error messages and the effort to enter their to the point where I think JavaScript style async await was actually implemented in the compiler so that they could recommend or explain how to do it in the Rust way, which I thought was very clever. Okay. So we should be we should be
40:15 done, I think, whoever very contrived hello world. But I I think And now we should be able to run it. We should be able to run it and pass in an argument and have it do with no argument, it should do the default. Hello, world. Ta da. Oh. We covered a lot of nice Rust things there. So that was that was great. Let's move on then. Unless you think there's anything you wanna add to this before we go back. Yeah. Somehow we managed to get into both lifetimes and all the different types of generics while
40:50 doing hello world. Yeah. I haven't like made anyone like tune out because it's like, I was coming here for faces. So No. No. No. I think that was that was great. So we have a letter r s. We have something we're shipping to the library. We can then add a main dot r s and that just worked. So I'm happy with that. Let's move on to the next one then. Okay. With this. Now yeah. We did all that. Oh, we didn't ask in line, but let's skip that. Let's do the word one then. So let's see.
41:14 Anagram Finder Project Setup (Cargo new)
41:28 W l words. I mean, hello world. We could create a peg word fail here. I'm gonna do cargo new. We'll see anagram finder and put up this here. I won't bother opening a new code. And we have our labs r s. So if we break this problem down, what we wanna be able to do first is the exact same as our hello world where we have some function of dynamic grams, which takes a word of typed string. I guess we could do that. And we want to return something here, an array or a slice of other strings.
42:17 Defining Anagram Function Signature & Initial Tests
42:25 Mhmm. Would that be this, This? No. It would be it would be around it. So it would probably so this specific signature would probably be a little bit difficult because, like, if we're gonna pass a slice, it has to be, like, reference to own data. And if we're only passing an input as the stir, then it'll be a little different. Let's let's have this function also take the dictionary as an input. So, like, all the words, like, Even that, I'm not sure. I think it might be best to just, like, return an like, a a vec of own strings and
43:19 not deal with the lifetime parts here. So get rid of the dictionary argument. Okay. So And then just the vec and then angle bracket string. And it should be a capital d. Capital d. Yeah. And then you can put to do as the macro in there, and it will not give you compiler errors anymore. Okay. So we also need then load dictionary. We'll just leave, like, after now. And let's just let's make this artificial for the time being. So let's say it can it finds no anagrams. What we're gonna do is call find anagrams or the words
44:15 anagram. And we should get back zero? No. None? We should get back a back, but let's just do just do vec exclamation mark and then, like, nothing. It should yeah. That'll work. Though I would normally do square brackets just as, a style thing, but I think macro rules will accept anything there, maybe. It depends on how you define it. Macro rules are a little weird, and I'm not actually that good at them. If you wanna get into macros, you should have Jam on next because he's very good at macros. Definitely. Okay. If we I forgot the super.
44:57 And if you wanna if you don't wanna keep remembering the super, go back real quick, delete that super. And then in the top of the module, like yeah. Just put use super colon colon star, and that'll just import everything from the above scope. Nice. Okay. Let's run our test. The right directory. Okay. Could not infer the type. That's interesting. It should be all in for the type there. This might be something to do with how assert is implemented. You can just I would like, just get rid of the assert equals and just do, like, if
45:51 and then find anagrams equals equals like, just, like, write basically, write out the assert ourselves. Or I guess it should be not equals because we wanna and then just panic. And then yeah. I'm surprised it cannot infer the type parameter there. I guess you could I guess it's because oh, I know why. It's because you can compare types that aren't the same. So the way that the equal trait is defined or the partial equal trait is defined allows for, like, lots it's it's a generic on the other side, essentially. And so the compiler isn't able to, like,
46:43 prove that because you're doing an equality on these two types, they should be the same type. And so we have to write it out by hand. We have to, like, basically, like, define the back. The easiest way to do that is just to not use the macro. And so instead of vec exclamation park, we're gonna just do yeah. Oh, yeah. That works too. Alright. Yep. That's that's definitely that works. And then there, we can do this the Yep. This way. And and words. See. Test pass. Okay. So I'm not gonna keep adding new tests. Oh, yeah.
47:42 Okay. Well, I was gonna say I'll just keep adding more assertions, but then I'll get confused. I don't like being confused. So it can find a single anagram. I guess it doesn't really matter. It just it finds anagrams. Yeah. I always struggle with the naming things more than writing the actual code sometimes. So string from and these I know these aren't real, but we'll just we'll make it past the fake case first. So if I say find anagrams or David, it's gonna return words. Alright? Sure. Yeah. Why not? I should just think of a real anagram.
48:29 Where did that pass? One test. Oh, yeah. Test. Yeah. That's better. You know any anagrams? I can't think of one off my head. I've got pressure. I would just do, like, v I d d a. Just fucking make up one. And then you probably should lowercase the the d in the David because we don't wanna actually and yeah. In your name because we don't wanna have to k. Okay. Yeah. We're we're not using the word fail right now, so it doesn't matter. You're right. Good call. Okay. So now this is gonna fail. So we need to make this
49:12 actually do something. So before we start doing a load dictionary, let's just say if word equals David. A vector of string from this is what pet TVD is all about. Right? This is this is professional software development folks. This is what we do. This is what we're paid for. Exactly. It was they said that if you got that wrong, I'm contriving it and still getting it wrong. So let's see what did I do. Find oh, I called it the wrong way around. Yeah. David should return. Vita. Okay. So one more test. I'll remember the annotation,
50:15 and this time but I don't need that anymore. We now can't take it anymore. We need another anagram. So let's just pick the let's take a look at this word style. Those are interesting words. Emphasis and c. That's my favorite word. The the word two. It's supposed to be every word in the English language. You know what? I'm just gonna Google it. Like, anagram, please. What's the first anagram? Elapsed and asleep. There we go. That's our test case. Oops. Okay. So we're gonna pass in asleep, and we expect to get back our words. We can just redefine that
51:13 as back string from a lapse. Mhmm. And that should fail because we haven't run any code yet. Okay. So I guess what we oh, we got some comment. I was too busy with my fake code there. Sorry about that. Yeah. Race car. Good one. That does the next the next test case. Stop stop trying to feed palindromes into our anagram machine. You're gonna mess it up. It's fragile. It's a fragile baby. Okay. So shall we handle the load of the dictionary? Sure. Yeah. Let's so the code test, what we wanna do is open the file
51:58 Dependency Management: crates.io & cargo-edit
52:06 Mhmm. Split on a new line Mhmm. And store a set of words. Simple. Right? And return and probably return the set of words. Return words. Okay. So when I can try this example, I thought it would be good because we'd maybe have to rely on a third party create. I don't know if this is all in the standard library, I'll use your guidance here. It is it is all in the standard library. Alright. Okay. Well, why don't we just artificially contrive magic words, a create enter a project just so we can go over that for people?
52:46 So do you have a favorite crate? A favorite crate? Alright. Yeah. Let's use struct ops. Let's let's make the interface to running this thing nice. Like, if you need a if you need a crate, struct ops are great crate to just pull in for like little tools. Okay. So for anyone who's not familiar with crates.io, it is just where you find other Rust libraries. You can search. You can click on it. It has a link to the repository here if you wanna go check out the code and a copy button for a copy and enter our cargo dot toml.
52:50 Pulling dependencies from Crates.io
53:21 For cargo dot toml, we just drop that in like so, and we now have struct available to us at our project. Can I actually introduce another way to add struct up to or to add dependencies? Do you have Cargo Edit installed? No. Is that a plugin? It it it'll be Cargo Add is the thing. Cargo Edit is the is the thing is the actual tool. If you yeah. So so look up Cargo Edit on GitHub or, like like, Google Cargo Edit. It should be from Killer Cup is, I think, the author. There we go. And so it should just give you, like
54:00 it's probably just cargo install cargo edit. Yeah. So run that. And this is this is, like you know, if you if you do Rust a lot, you should have this installed, in my opinion, because it's just it's like I I I hate having to, like, look up the version number and be like, what exact what's the newest version number? I go go to creates, find the thing, copy it. It's just too much work. I I just wanna write cargo ad struct top and have it find the version number, put it in my TOML file, and be
54:30 done with it. And that's what that's what cargo edit does. So you just you just have to know the name of the dependency you wanna add. And you could do, like, cargo add struct op dash dash dev, and it'll add it to your dev dependencies. So it won't even, like, it'll even make the section if you don't already have it. It's it's very nice. Oh, nice. Highly recommend. Oh, no. We are waiting on the infamous cargo build things. Yes. This is a maybe we could go to the the hello world example. I was thinking because
55:01 Command Line Parsing with structopt (Demo using Hello World logic)
55:01 I actually kinda want to introduce this for when we were doing that example, which is what made me think of it when you asked for a crate. Oh, okay. So we could change the the hello world example to instead of using stood n to use struct opt. So Okay. So if I just paste this n oh, no. It's not on my buffer anymore. Struct op equals star. That works. Just just do 0.3. Yeah. It shouldn't do star usually in in Rustland. They're, like, very, very strongly discouraged to do. I just copied it. Yeah. It it it'll
55:43 it'll fill in the 21 automatically. And I don't know. Keep in red squiggles. Maybe Rust Analyzer is just a bit confused today. So Yeah. I also saw when you opened Versus Code that you're not on the current version of Rust Analyzer. So you might wanna, like, close it and reopen it and click the download. But I'm not sure if you wanna, like, wait for more things to so so are you familiar with struct opt? I am not. No. Okay. So it's kind of all in the name. You define a struct. So just like after main or before, you know, whichever style preference.
56:16 So you just do, like, struct and whatever you want to have, like, the type be. I would just do, like, either options or args, like, whichever you prefer. Sorry. I don't understand. Oh, you need to give it a type name because this is like, we're defining a struct. Right? And so, like, after the struct keyword, you have to give it, like Oh, alright. Okay. Okay. Fine. Whatever you're naming this type. Hello, Prems. Perfect. And then you're just going to push a derive in front of the the struct up. So derive and then parentheses. We have an extra c in there. And
56:58 then I think it's struct up all lowercase because you have to it's, like, scoped. It's still, like, the the, like, the macro exists in the namespace of the crate. And then cap yeah. That one. Big, capital s, capital o. Yep. Oh, my computer is struggling. Having a good time. Yeah. I can't I can't type anymore. Wow. That is exciting. Maybe yep. Yeah. We'll we'll just watch that. Sorry. So yeah. So I'll I'll explain what struct opt does. So struct opt, it generates a and it's it's a library that's built on top of CLAP. CLAP stands for command
57:44 line argument parser. It's like the premier kind of biggest, most used argument parser library in Rust. It's not the only one. There are lots of other good ones. A lot of people don't like, a lot of people don't use clap because it generates a lot of code. And so, like, if you have those constraints, that's why, like, Google wrote r h, which is kind of like, we wanna have a struct up that's actually good if you have, like, you know, binary size constraints. And so we're gonna generate efficient code, whereas struct up and clap are kind of like,
58:15 we wanna give you every feature you could possibly want from a command line argument parser, whatever the cost. And I think it's usually worth it personally, so I just use struct up. But there are lots of options out there. But clap gives you the argument parser. And then struct opt, what it does is it looks at the definition of a structure and infers a lot of, like, the what type of arguments that you would get in the the argument parser from the structure. And so, like, for example, if you have a name and a string, right, what's what's referring right
58:49 there is that you have a an argument where the flag is called or the name of the argument is name and the type that it expects to get is a string. And so it will parse it as a string. If you were to put a u h there, it would only accept numbers and probably only numbers between zero and two fifty six. And, like, if you passed it like, even though the like, at the command line, like, when it gets it out of stood args, it's, like, actually a string. But struct opt and clap are gonna parse
59:17 it and make sure that it, like, actually matches the exact type you have. And then you can put attributes on each argument to kind of basically, like, add kinda like like to invoke the functions you would normally do when building a command line. So if you do before lucky number, put a attribute. So as hash, yeah, opens square bracket and then struct opt and then parentheses short, comma, long. So this is saying we wanna have a long and short argument. So now this is basically turning it into, like, a flag. So you can do dash l, I think,
1:00:07 would probably be what it generates or dash dash lucky dash number or maybe lucky underscore number. I'm not sure exactly if struct up normalizes the underscores. So this one is now an optional argument because you've added those, whereas the name one is a positional argument because you don't tell struct up that it's a a flag. And then you can also do something like you can put string for name in option in an option, and struct up will automatically, like, assume that this argument doesn't have to be present. And so now you can pass in you could like, with
1:00:44 this command line, you could pass in no arguments and it won't give you an error. And if you passed in one argument with no flag, it'll know it's the name. And if you pass in, like, a dash l, it'll know it's the lucky number. And so it's, like, generating all of this just from what you put in. You can put in a spec if you want multiple arguments. It's like it's so intuitive and easy to remember for me. It's like the best thing. I fucking love it. Okay. So let's go through the process of removing this lane and actually
1:01:14 getting our struct from struct up. What what's the process of that? I just did, like, options equals, I guess, struct up something? You know, it's just gonna be held because this is this derive is implementing a trait. And so I think it's just gonna be hello params colon colon from underscore arcs. And then this will probably give you a compiler error, but compiler error will be helpful. So we, like, we just wanna look at the compiler error. And it should say it should say that we're missing a trait in scope because, like, you can only
1:01:54 use trait methods on on types if the trait is in scope. And so I think there is also a struct opt trait that this thing is actually implementing. So we we're gonna have to do, like, a use struct opt struct opt, probably. Yeah. So use struct opt struct opt right there. So copy that line near the bottom. Just shove that in there somewhere. Now Yeah. Now now we just need options dot name dot as d ref. And then so you can do cargo run and then dash dash. So the dash why does it say that's required?
1:02:50 Oh, because it's not an option. You have to have so even with the flag, it has to like, the option is is, like, you need to specify that. K. So we can add a cargo run on it. So why don't we get hello world? I noticed that I added a a help So that's gonna that's gonna give you the cargo because you're still in the cargo command line. So you do a dash dash nothing after that to say, I'm at the this is the end of the cargo arguments, then everything after that will be passed into
1:03:19 the binary you're running. And so that's how you you get the help. And so by just using this struct up thing, which is relatively trivial, we now get this usage which says, hey. You've got a positional parameter and some optional parameters here. Mhmm. And if you and also if you go back, you can you can go back to the hello world example. You can document it. So just put a doc comment line above the name and it's like the it's, like, the per the the thing that we're gonna say hello. It's a triple forward slash.
1:03:51 Triple oh, yeah. Of course. Alright. I was in JavaScript mode, I think, there or something. See. Or Elixir. So we can say this is my help. Yep. And that shows up as the the org's name now says it's mail text. And you can do the same thing for lucky number. And, probably, I think you could put a doc comment on the struct itself, and it'll probably be, like like, kinda like the paragraph summary at the top of the the thing. It's it's all very nice. Cool. Very cool. Great. And we have cargo ad now. Yes.
1:04:35 File I/O: Loading the Dictionary (`BufReader`, `lines()`)
1:04:39 So that is one cargo ad. It says, okay. I want the name of a crate. CD struck dot. I think we already added that. Why do I move it from the Sure. Sure. They're gone. And we see it's dropped out. Alright. So it resolves the version for us. It's added it here. Mhmm. You also said I could do dev? Yep. Yes. Okay. Okay. That's now my new favorite cargo plug in. Okay. That is a nice little segue there. Although, I think my computer was a little hot. Let's do this. I'm sorry. Okay. So we we said we could do
1:05:00 Loading a file with BufReader
1:05:23 this all from a standard library, but we've now explored the crates. So let's get started with opening the file. I most I don't think we'll we'll get through this with the time remaining. So we'll just do a couple of bits and pieces and then we'll we'll wrap it. Okay. So let's do, like, just the easiest thing we can do. So there is a helper method in the standard library that will just read a file. Right? And so this is gonna, like, shove it all into a string, and it's it's, know, it's gonna make a big allocation, and then we're gonna have to
1:05:58 cut it up and make a bunch more allocations. So it's not necessarily the most efficient. Do you wanna do it the the the easy way or do because, like, do you wanna move forward or you just wanna just, like, show people how to do file IO correctly? Your preference. Are we trying to it correctly? File IO. Let's just do file IO correctly. Yeah. So can you open the browser? And then I want you to just type in the the in the URL field, stood.rs/buffreader, b u f r e a d e r. And so this just automatically
1:06:36 goes to the standard library docs and does a search. And so now you can click the first one, which is struct. And so this is this is the important type for doing IO in Rust correctly. Like, you can open the file directly and read from it directly, but, like, that's gonna do a ton of operating system, like, calls. Right? So it's gonna be a lot slower. And so you're always supposed to use buff read around the file, which will, like, actually allocate a buffer that you can read a bunch of bytes into, and then you can
1:07:07 read out of that at your leisure, and it makes it all much more efficient. And so then let let's just do a bit more explanation. Then the next thing we're gonna want is, lines, which I think is from the buff read trait. So if you look at the trade implementations on the sidebar, it says buff read down at the very bottom. Right there. Yeah. And so if you click on the traits, like, just click on actual the buff read word, it'll take us to the trait, and then we can see the provided methods on the sidebar.
1:07:42 And this lines function, the one right above it, this is the thing we are looking for. This is going to return an iterator over the lines that splits it at the new lines, trims the new line, and just gives us each word of the file. And so what we're gonna do is we're gonna open the file with stood f s file open, and then we're going to shove that file inside of the buff reader so that it's able to buffer the output. And then we're going to call the lines method through the buff read trait on the buff reader, and
1:08:13 that'll give us an iterator that we can just do, like, a for each loop or we can, like, do a collect or whatever to generate our collection. Alright. Cool. So let's back a little bit. So I might just copy these first two lines. Right? Yes. Okay. So we're gonna open our file. Words dot text. Could you I know that's this question mark here won't work. Right? Because we don't return a result type. Correct. Now is that standard to do within library functions or just the main? Like, what's the the rule for that? The rule for returning our result?
1:08:40 Handling File Open Results (`if let`)
1:08:54 Or just would would you do that in this function or would you just actually handle this case properly? Like, what would be your preferred approach? So, normally, I would propagate out the error. Like, if if I don't like, if there's something I know I can do in this case, like, there's not a file, I know that I'm gonna just return an empty dictionary, then that's what I would do. Like, I would just, like, do an if let equals the file, and then else I would just immediately return. Oh, sorry. Sorry. It's instead of the, like,
1:09:31 if let f or not, like, you have to do a let first because we're gonna have to get the binding out. So let f equals if let. Okay. The capital o, parenthesis, f inside the parenthesis, and single equals, and then delete the semicolon, and then add up add up brackets, and then put f in there, then else, and then just return, you know, return back. Yeah. Sure. And then a semicolon after the final one. And yes. Can you do control dot? Never mind. You you already got it. Okay. So yeah. Okay. So I understand that I've let
1:10:33 we're okay. So we're basically, you know, pulling that out, making sure that it's okay, not an error. Mhmm. Else let equal tier teams slightly proposed. Yeah. So this is this is something there's, like, proposals for fixing this. And, eventually, we're gonna basically have the inverse of an if let where there'll be, like, a let else. So you could do let f equals like, let okay f equals file open else return, and that'll kinda, like, you know, bring it into the outer scope automatically. Instead, because we don't have that, we have to, like, use the fact that everything in Rust is an
1:11:17 expression. And so this if statement, like, with all its branches can have a value as long as every branch of the of the if returns the same type. And we cut even though we don't actually do that, we only return the type in one of them, we can get away with it because of the never type in Rust. So there is a special type in Rust that says just, like, basically, you're never going to execute anything after this. And it's for things like stood exit or a return. Like, you know when you return, you're gone. Like, you're out of that function.
1:11:52 So everything after that is never gonna get run. And so Rust uses the return type of a of a return expression to then know that every expression after that isn't run. And when it gets an error type, it will treat it like any other type for type checking purposes. And so even though the never isn't a file, it's like it's file enough for us. So it it'll type check and just let us keep going because it knows that it'll never actually try and exit that else statement. Okay. Random thought that popped in my head when
1:12:27 you were kind of explaining that. We Mhmm. We used unwrap r earlier. Is there an error r? It's still called unwrap. Yeah. Option and result both use the same kind of like Alright. So you could do unwrap or but if we did that, we would not be able to call the return That's why we have to use this if let because if let is, like, the fundamental language control flow things and so it can do, like, all this fancy stuff of, like, you know, exiting. But if you do a unwrap or, it's just an expression.
1:13:06 Cool. Okay. So we've opened the file. We've returned an empty dictionary, but it doesn't exist. We've got our buffer there now. Going back to this code, I guess we want to we followed the buffer tray. We clicked on this. We do we want what are we copying now? Sorry. So as you're basically gonna copy it I mean, it's just the the third line, the thing after cursor. Because instead of cursor not not lit not the the third populated line. Sorry. I wasn't counting the empty lines. Yeah. So but we're not using cursor. We're using buffread. So it's
1:13:45 where the cursor goes, we're gonna just call buffread. So reader. And, yeah, we can leave the unwrap there. That's that's totally legit. And this is gonna complain. It's the same thing that happened with struct opt where you don't have the trait and scope because that method is implemented on the trait, not on buff reader. And so, yeah, once you add buff read, it will work. And the buff read, each, like, each line is a result because it could have, like, had an error when it was reading the line from the underlying file on the file
1:14:17 system. And so that map unwrap is just, saying, if there are any errors here, I just want you to panic, which I think is fine. We we don't have to do, like, we could we could skip them. We do you wanna skip them if they have errors? I don't know. Let's just panic. It's fine. Yeah. We'll panic. Okay. So now we have the lanes iterator. If we wanna convert that into a a set, I guess. Mhmm. Is that the hash set in Rust, isn't it? Yeah. Sure. We can use hash set. Is there something else you would use? Well, we
1:14:36 Converting Iterator to Collection (`collect()`)
1:14:51 used the back in the other the else statement on line 10. So so I was thinking it would just be like a back. But then we don't get, like, the the properties of, like, an actual set, so we could have duplicate words. But I think it's fine because we're, like we already know that, you know, the words file is, like, trusted input. Oh, we hope. Should be alright. Okay. So what do we do with our lines ator here then? So this one's actually this we can just use collect, which is, like, one of the coolest
1:15:20 Collect
1:15:25 one of the cooler functions in the standard library. And so collect uses a bunch of type system awesomeness to basically generate the type of collection from the iterator that you, like, you want. Like, you tell it, like, turn the like, what type to make from the iterator. And you can make any type. Right? And so, like, I could you could say collect colon colon hash set, and it would generate a hash set from the iterator. Yeah. You need the the square brackets. We don't we don't need the colon colon at all. Right? Because we can let Rust infer
1:15:59 this. And so we literally just do collect. And then you probably just you just get rid of the return. I mean because, like, you can have the last expression in a function is always the return, which is, like, what we're doing on line eight. We're not, like, actually returning it. So you can do that. You don't have to call return. We've been doing that in a lot of places, but it's entirely unnecessary. It's like, I think you get warnings in Rust if you do it. It's like not idiomatic. Okay. I'll try and get that bad habit
1:16:06 Anagram Logic: Iterators, Filtering & Sorting
1:16:28 out of me for sure. So that's our load text. Right? That was just using the standard library. It was pretty simple. Yes. Okay. So let's see. So here, we want the dictionary equals load dictionary. Mhmm. And then let's how long have we got? Okay. Like, ten minutes. So let's iterate over the dictionary and that's just a really simple test to see if it's the same number if the length's the same and we'll Mhmm. Skip the word. If they're if they are the same, then we'll see what we wanna do after that. So Sure. We have got a dictionary and then are
1:17:00 Iterators, Filters, and Map
1:17:15 we doing ator? Yeah. We're we'll definitely use the iterator interface. Iterator is like real good. There's we can chain a lot of functions to kinda like build up the rules that we wanna apply to this iterator. Okay. So how would we add some sort of filter on the the length of the the two strings? So there so there is the filter method on the iterator trait, which takes it as a self parameter. So it's like a method on thing. It takes an iterator and you and then a closure. So filter. It it's not let it's not completing, so
1:17:53 I just assumed that it was wrong. Yeah. It should work. It should it should still work. And then you're so you're gonna put that and then l and then l dot len equals word dot len. It should be parentheses. So those are methods. Ah, okay. And then so now we have only words of same length. And then next, we need to, like, do the actual anagram thing where we, like, make sure all the characters are the same. Okay. Why don't we simplify this for the time that we have remaining and call this this count anagrams?
1:18:48 Oh, no. Because Rust don't actually do it. Yeah. We just count how many words have the same length over there. So this is gonna return u u 32. We'll return zero as the default case, and let's just get rid of our tests. You can probably I think you don't need a default case because we're we have the iterator is already, like, implicitly going to have a default case of, like, zero length iterator. Okay. Well Oh, no. No. No. You can you everything else you did was good. Oh, well, I was gonna we we can do account if
1:19:27 they have the same length. And I was like, but it's actually not gonna be difficult to work out if it's an anagram, is it? I mean, if we've already got the words of the same length, we really just wanna create a hash set of all the letters and then just say if they're equal to the hash set of the letters of the other words. So we could probably just do that. Right? Sure. Yeah. Let's do that. That's easy enough. So let's let's do let's write a word to to to letters thing that takes a set a string.
1:19:57 Okay. So word string return. To hash set. Hash set of Or should we hash map? Or I think we we could just, like, sort the characters and then say if they're equal. Right? Yeah. I thought if we did a hash set, I mean, they would be the same. But a hash set would require that the characters are unique. Otherwise, it would, like, deduplicate them. So if you you'd you'd end up with a Vita with one d. Okay. So really what we want is word to sorted errors. Here's me thinking this is a really easy thing to do, and then I or even
1:20:40 turn up with an algorithm. Okay. So I might just do, like, a vec care. I don't I don't know if there's, like, a way to sort a string in place off the top of my head. And then I think it's just you're gonna do word dot cares dot collect, and then let that equal some back or, like, colon so you can do after the collect before the parentheses. And you actually don't need the the car there. So colon colon parentheses square bracket or angle brackets and then back and then angle brackets with an underscore in between.
1:21:30 Because the it already knows the chars iterator is a is an iterator or characters. And so it's able to infer that inner type. It just can't infer the collection type. And so you can only specify the collection and let it infer the the item from the iterator. And then so we do need to sort that still. So we'll have to do, like, a let unsorted equal online 23. Okay. So let unsorted. Yep. Let me look up sort in this here and see where it lives. I never sort things. So it's on the slice type. So it should just work on back because
1:22:19 that d refs to slice. And so you're just gonna call that sort. And you're gonna have to put a mute, a mutt after the the let or yeah. And then unsorted, and then, like yeah. That does not return the value. So it's just gonna be a a colon afterwards, and then you have to return unsorted. Just unsorted by itself. Yeah. Remember, you're trying to get out of that habit. No return statements. Okay. Cool. And then now, actually, you should be able to get rid of the turbo fish and collect because I think it will still be
1:22:59 able to infer. Let's see. It might break because of the sort. Let's see if it breaks. Just, like, get rid of everything. So how do you Yeah. Yeah. Does does it break? Yeah. It breaks because it needs to know it needs to know by the time it does sort. And then the ord, it's not able to figure it out. Okay. Oh, well. Okay. So now that we have a dictionary with an iterator and a filter, are we doing some sort of map or iterator? Let's do let's do map and then just word two sorted letters. That's it. No closure.
1:23:35 And then just delete the parentheses and the arguments. Like, it's literally we're just gonna pass in the function. And then let's before we do this, let's make the sorted version of the input word. So let's do, like, after dictionary, let's say, like, let expected equal word to sorted letter word. And then we're gonna do a dot filter and disclosure. So, like, a, you know, vertical line l or whatever w w equals expected. It should be assign this or just return it? Yes. So we can if you delete the VAC on line 45 and then just put a collect after the
1:24:24 filter, it should work, I think. Let's see what Rawkode said. Okay. So can you just before the map, do a let's see if we can do, like, dot map and then parentheses w. Oh, sorry. The and just do ampersand star w. Ampersand. Sorry. Not underscore. Let's see if that works. Does that does that what does that give us? What does it say? Do two stars. And then put an ampersand before the w and filter. Or this one? No. The first one. And then then you're gonna have to do a final shit. See, because we're mapping it. We're we're
1:25:42 destroying the original word. And so we've we we've got ourselves we we can't do this as, like we can't we can't chain this the way I wanted to. So we have we have to do it all as, like, I think a single filter map. So delete everything after the first filter of the LEN or just, I guess, delete everything except for the collect, and then put dot filter map. And then it's gonna be word, and then yep. So we're gonna do let actual equal word two sorted letters and an ampersand w, I think. Or we
1:26:31 might just be able to put w. I think w will just work. It's already a reference, so it'll already deref. Yeah. So it's we still have to, like, actually do the thing. And then do if actual equals expected. Double yeah. Then some w. Else, none. Now why is it upsetting us now? Because we're doing okay. So instead of iter in the first line, into iter. You tell me what the difference between those are? Yes. So into iter is that, like, the actual fundamental trait that is implemented on all these types. Iter is it's like a convention, like new,
1:27:08 Iterator vs IntoIterator Distinction
1:27:26 where it's, like, the way to say, I want to, like, the type and iterate over it. And so iter is always going to, like, intro to like, it's going to reference the thing and give you a borrow over references of, like, the borrow on the container that it makes. Whereas into iter is always going to be, like, based on whatever type it currently is, I am going to convert this into the iterator that is implemented for that type. So for a vec, you have, like, a couple of into iter definitions. So, like, if it's if it's a or just the back itself,
1:27:57 into iter will give you the item owned. If it's a reference to a back, it'll give you a reference to each item. If it's a mutable reference to a back, it'll give you a mutable reference to each item. So this is into iter is the thing that the four trait work the four loop syntax works on. So that's why you can do something like four word in in ampersand vec and have it just, like, give you not, like, destroy your vector and actually, like, iterate over it by reference. It's because it's calling the, like, the specific into iter implementation
1:28:29 for a reference to a vec. And so in this case, we want to do owned iteration of the back. So we wanna keep the strings that were in the dictionary and return those out as owned strings to the final thing. So if we, like if the if the dictionary were static, then we could, like, you know, return static strs. Or if the the dictionary's input, we could have a borrow to the dictionary. But because we've, like, kind of, like, constructed this as a temporary value internally, we have to, like, destroy it and return pieces of it, really.
1:29:00 Okay. Alright. Let's test if this works then. The test will fail, but, I mean, we should We might get, like, a lot of extra anagrams if we're lucky. But we can probably remove this truck top that let's see. I'll give it a few seconds to see if it wants to move you on by. I guess what we can is there a stair contains? Or I guess I could do You could just do a search and then find anagram stock contains. And it's not gonna be words. It's gonna just be like parentheses elapse. Like, you're not gonna be able
1:29:12 Testing & Debugging Anagram Finder Logic
1:29:45 to do because contains only is gonna it's gonna be the item of the back. You're gonna, like, look for a single item. It's you're not gonna be able to, like, compare a collection to a collection with the contains method. Started to string from one. Yeah. What have I done? Oh. It expects a a reference to a string. Yes. The the the borrow checking and, like, lifetime like, the type type rules when it comes to references can be, like, annoying and weird. And it's if you're ever confused by them, I highly recommend looking up and, like, really familiarizing yourself
1:30:30 Helpful Resource: cheats.rs (Language Sugar)
1:30:37 with all the syntax sugar that exists in Rust. There's this awesome website called cheats.rs, which, like, you can bring it up right now. I wanted to show you the section. Sheets .rs. And then on the behind the scenes, scroll down. It should be on the left column. Yeah. Sync language sugar. So these all of these sections are, like they're real good things to read. If you're not if you don't wanna read anything else about the language, like, read these things and you will have, like, all all that, like, I'm just, like, peppering ampersands and stars in
1:31:11 there to try and, like all of that makes sense if you've read these sections and understand how dref works, how coercions work, how lifetime malition works, how method resolution works. Like, all this stuff is, like, the thing that makes Rust, like, not really verbose. Like, it would be way more verbose. But, like, they can't fix everything and so, like, not if you don't understand the kind of things it's trying to do for you behind the scenes, you're not able to help it do those things for you. Awesome. I'll definitely make my way through those. I I don't wanna keep you too much
1:31:40 longer because we're kinda over where we said we'd be. So I'll just run a test and we'll see what happens. Well, it did work. It found an anagram on accident. Well, I think it was okay with the the sleep one worked. I think it's just this one found an anagram. Yeah. Yeah. Because because it didn't expect you to have any anagrams, but it actually does have anagrams. That works. We special case it anymore. There we go. Awesome. That was really enlightening. I think even though the examples are particularly trivial and contrived, like just exploring the language and you bring in your knowledge
1:32:17 Conclusion
1:32:26 and sharing that there was was really really cool. There was a lot to digest there, and I hope that other people find that. No. No. Don't be sorry. That was a miss. I hope other people find that as useful as I did. So I just wanna say thank you for taking some time out of your day today. No. No. But going through that with me and, you know, hopefully, lots of other people will find this very, very useful. So thank And, Mike, you're welcome. Thank you for having me on. Like, it's it's been really fun. So
1:32:50 it's a good morning, you know, with fun teaching people Rust. That's that's like my favorite thing. I started Rust Mentors to do this exact thing. So, like, absolutely. My pleasure. Awesome. Well, thank you very much. You have an awesome day and I'll see you again soon. Okay. See you.
Technologies featured
Meet the Cast
Stay ahead in cloud native
Tutorials, deep dives, and curated events. No fluff.
Comments