Build a Command Line Interface in Rust
with John Breen
The Rust Language ecosystem is gaining steam as a powerful, friendly, and FAST way to build all sorts of things. In this episode, John Breen will teach us how to build a custom command line interface using Rust!
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Jason: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show we're bringing in John Breen. John, thank you so much for joining us today.
John: Hey, thanks for having me, Jason.
Jason: Yeah, absolutely. I'm super excited to have you on. I'll be honest. The way that I actually got -- became aware of you in the community was through the epic burn down of Basecamp. You had collected kind of a whole bunch of the news around that, but since then, I've been following your work, and one of the things I've seen you work on that I think is really cool is Rust. Before we talk about Rust, I would love to hear about you. For folks who aren't familiar with your work, you want to give us a little background?
John: Sure. So yeah, again, thanks for having me. My name is John. I kind of bounce around between stacks and stuff, hence my interest in Rust. So my day job, I work at a company called Laerdal Labs. You might see me yelling about web video and stuff on Twitter because it's difficult.
Jason: Sure, sure.
Jason: Yeah, for sure. I feel like this is one of those things where there's a little bit of -- I always want to learn new languages. Rust has been really high on my list because so many people in the community that I respect are really excited about Rust. But you know, especially now with my new job, I have a really hard time finding time to just like try new stuff. So one of the things I like forward to most with this show is that this show is my excuse to learn new stuff, right. So we've done a couple episodes on Rust before. We've had like Chris Biscardi on, Prince Wilson taught us Rust. Like, there's so many cool things that I've seen people do with Rust. Whenever I hear people talk about, like, the speed of it, the type safety, the helpful error messages, there's so many things to love about Rust as a language. But I'm kind of curious, what have you found to be, like -- in your experience, especially as somebody who's used a bunch of languages, what are the things that stand out about Rust and make you excited to keep using it?
John: I think, you know, it at least severely mitigates it. The concept is that is the compiler does a lot of work for you to see what memory you're allocating and making sure that you don't do things in a way that gets into those pitfalls. So something you'll notice as you start to use it is the compiler yells at you a lot, but it's a very friendly yell. That's something you mentioned, the compiler is fantastic. It's one of the best compilers I've worked with. Some other language, the compiler, something will break and you have no idea why, and the stack trace makes no sense. Everything I've run into in Rust, it's like the compiler breaks and then it tells you, oh, hey, you're doing this thing on this line that you maybe shouldn't do, and I'm not going to let you run this program. But here's how you might fix it with this specific error code. Go to this specific, you know, link or go here to see how you might manage this. So it kind of walks you along that way. There are ways to do things, to hop out of that in a more advanced system and hop out of the memory safe and explicitly do things in an unsafe manner, but you have to kind of go out of your way to do that.
Jason: So another thing that I'm actually really interested in is the Rust community. Like, I've seen very, very good things about the Rust community. It seems like it's so supportive. It seems like it's very inclusive. As far as I can tell, it's really welcoming. I haven't seen any memes about like somebody asking a question about Rust and getting told like, get out of here, noob. It seems like a very welcoming place for developers. Have you found that to be true as well?
John: I have, yeah. You know, any community is going to have its ups and downs, but so far -- and I've been part of several different developer communities. For example, I've contributed to the Starship terminal prompts that is written in Rust. The folks that maintain that are fantastic and super helpful and really a great model of how maintainers can really do it right. Also, the broader community around Rust, it's a lot of folks who maybe didn't feel included in other communities. It's people who really enjoy having fun with the language and, like you said, I haven't really experienced a whole lot of the -- well, that's a terrible question kind of stuff. I don't know what specifically it is about the community. It does feel like it's more inclusive. It's not the typical tech bro orange website, folks who will assume you know everything about everything and know all these really obscure words. So it's been wonderful. I think that's a huge part of it. Programming and being an engineer, at a certain point it's really not about the code. It's about the people you're writing with. If you're not enjoying the thing that you're doing and you're not enjoying the people you're around, then it's kind of -- it's a lot harder to get work done. It's a lot harder to actually build things that people want to use and do the least harm possible to people.
Jason: For sure, yeah.
John: If you're working with people you really care about.
John: So, you know, I mentioned Starship as kind of a prompt. A lot of people use things like -- there was a thread recently on Twitter about how to pronounce it. I like all of those. I have to give a shout out to Git. I've used it for years and years, and every time I think I understand what's going on, I learn that I really don't. There's fun ones, like the ones that will take whatever you input and output it to like -- actually, let me look at my terminal and see some of my favorites. You know, there's just a lot of, like you said, as a developer, especially as someone who's been doing a lot of product development, a lot of web app development, CLIs often fade into the background. You think of application, you think of let me set up an API and do these things. As developers, we're doing a lot of things on the command line every day. It also kind of harkens back to being able to do some small piece of functionality or something totally fun like the one that kind of inspired me to want to talk about this, a command line I was building to journal a little bit. It's inside of my terminal. I have my terminal in front of me pretty much all day, and I need a reminder to write down just a few seconds of thoughts every day of what I'm thinking, what I'm feeling as either part of some therapy or mental health work. It was just a cool little idea I had to prompt me with some questions and maybe answer them.
John: For me, it's a way to bring the fun back to development in some ways. I've been throughout my career dealing with burnout off and on. It was just a nice way to bring something different and bring some cute little tools into my life.
Jason: Yeah, I see the chat is calling out things they like. JQ. Bat is a Rust extension. I'm not 100% sure what that does. I've never looked at it. I feel like I know about a lot of tools, and I don't use any of them. I'm very curmudgeonly. I refuse to try anything until I have a very acute problem. (Laughter) But so, okay, at the risk of -- you know, I could ask so many questions about this, but I think it would probably make sense, let's dive in and start looking at some code. Let's see if we can build ourselves a CLI. So I'm going to jump into this screen here. Before we get started, let's do a shout out. We are being live captioned right now. We've got Rachel in with us today from White Coat Captioning writing down all the things we say. Thank you very much, Rachel, for being here. That is made possible by our sponsors, Netlify, Fauna, Auth0, and Hasura, all of whom are kicking in to make the show a little more accessible to more people. Today's guest is -- let me throw some links, huh? Today's guest is John Breen. Make sure you go and follow John on the tweeter. Lots of good information on there. It's like more than just -- this is not a tech feed. You're a whole person feed, and I dig that. So definitely go. It's a good follow. We're learning Rust today. So make sure you go and get into that if you need it. This is where we're going to find documentation and those sorts of thin things. We were talking about Starship. I'll throw another link in the chat. We also talked about Rustlings. And this is when Prince came and did an intro to rust. We aren't going to do a full intro because we want to make sure we get through the CLI. If you're a first timer and you want to learn, this is amazing. Prince is a great teacher. He's a wonderful person that's going to bring a lot of joy into your life. Okay. So with that being said, here I am. I am in VS Code. I have nothing but a readme. Let me bring you in. There you are. Okay. So we are now doing a VS Code live share. I asked John to live share with me because I was worried that I was going to forget all the syntax. So I'm now ready to start. What's the first thing I should do?
John: So I don't know that we mentioned it yet, but the main tool or package manager, speaking of CLIs, for Rust is cargo. So that's kind of a package manager. It's a build tool. It's very similar to NPM, if you're familiar. So yeah, that's how we create new projects.
Jason: I do have it installed, I found out.
John: Right, so cargo. What's the command? Cargo new, I think. I don't know if there's an init.
Jason: Oh, that's a good question. Let's see.
John: I'll do that in a folder that doesn't exist yet.
Jason: If I need to, I can create like a new folder in here. Is it going to let me do --
John: Ah, so you can use cargo init. Create a new package in an existing directory.
John: Yep, there you go, Chris. Thank you.
Jason: Happened so fast I didn't even notice it. All right. So I ran cargo init. That gave us a cargo.toml. It's nine lines of code. So we've got the Rust CLI. We've got an empty dependencies array because it's TOML. Then let's see, we have a Git ignore that skips the target. Then we have this main.rs with a hello world in it. Perfect. Okay. So, so far I get what all of this code does. And if we run this then -- so if I want to run this, I would do -- just cargo run?
Jason: Hey, there it is. And if we go in and change this and we run it again, all right. We're writing Rust, everyone. So the next thing we would want to do is what?
John: So if we're doing a command line app, there's a dependency I really like. It's called clap.
John: It's command line art parser, I think is what it's short for. We want to add that as a dependency.
Jason: I legit didn't put together that was an acronym. Somebody was like, oh.
John: I may have made that up, but it makes sense to me. It gives you various patterns for working with -- so Rust does have a built-in art parse, what's it called, library or functionality. I like to -- CLAP gives us a few patterns. You can do it with YAML, I think. You can generate one with macros. What I really like to do is you can generate it in code with a builder pattern so that way it gives you a more kind of ergonomic interface. We can go into our cargo.toml file.
Jason: All right, here we are.
John: I lost my live share. There it is. We can add our dependency there. So that's kind of the -- I think the newest version is 3.0 beta 2.
Jason: So we add the name of it and then do equals and whatever version we want to bring down. Is this a pinned version? Like, it'll never update even if a beta.3 drops? Or does it do kind of like the --
John: It should give you that version. There's also a cargo.lock file right there. I think it might do similar to NPM where it generates a very specific -- or it uses a very specific version.
Jason: Cool, cool. Did I do the -- I did alias corgo to cargo. Yes, I did that.
John: I thought I just didn't understand the font you were using. So yeah --
Jason: Oh, my god, I did that subconsciously. (Laughter) Okay. I definitely spend way too much time --
John: Talking about corgis.
Jason: (Laughter) Okay. Good. All right. Now we have this dependency. Do I have to do anything to install it, or will it just install?
John: So the next time you run cargo run, it should automatically bring that in and compile it alongside of your application. So we can actually go to the page for -- let's see. There's the Twitch. There's the chat. Oh, the chat is hiding from me. My computer is just not cooperating with me today.
Jason: Okay. So here's our -- oh, you've got the builder pattern.
John: That's how I prefer it. Others might have other preferences. There's various different ways to use macros and YAML. So what it does here is create an application inside of your main function. You get a name and a version and everything. Then you can give it different types of arguments.
Jason: Got it, okay. So let's do this. First of all, chat, what is our app today? What's our CLI for? What are we gathering information about? While you do that, I'm going to set up our basics. I'm going to bring in clap. I'm using clap. Then I'm specifically using arg and app. All right. So this is sort of like named imports. Is that right?
Jason: Nice, yeah. Nikki came in first with burger builder. I'm all in on it. Smash burger (Laughter). I love it. This is an opportunity to troll Sarah even further. So let's build a Rust CLI that helps you build a correct burger. For those of you who aren't familiar, I built this website with Sarah to give each other crap about our preferred burgers. I do a smash burger, and she does a sous vide burger. She just tosses it in the bath to boil. Her animation is better. My burger is better. (Laughter) Anyway, if you want to play with that, that was a fun site. Let's not lose the plot. I'm not going to do that thing where I rabbit hole for 45 minutes on something unrelated. Okay. So we've got a burger builder. I need to set a version. So I'm going to set the version to, we'll call it 1.0. I need an author. Just put this in here. Okay.
John: One thing I also really like about all this library is they have some examples for ways to, like -- for the version and the author, I believe, they can macros that you can get the version off of the cargo.toml. So it pins the version of the command line application to the version of your project itself. So you don't have to define it in two places. Just fun facts.
Jason: Yeah. Okay. So I've got the stuff that makes sense to me. I'm giving it a name. Version, author, and some about. Now we're getting into args. I can see that the args kind of have nested configure. So can we talk through kind of -- let's maybe think in comments through what we want to do here. So I would like to make this a little wider so we can see what we're doing. And we want to, let's say -- come on. Look at co-pilot knowing my heart. I feel like we should ask for, like, some different toppings that you would add, right. Select toppings, and then we probably want like defaults to like lettuce and pickle or something.
John: That's contentious. (Laughter)
Jason: Then we want to select style. We would want like options, right? So we have options. You can choose -- look at co-pilot go. It really is upsetting how close to correct it is. So we can say smash or wrong. Then we would default to smash. All right. What else? I mean, maybe we can start there. We've got a multi-select and a single select. With these you want options. So your options would be like an array of lettuce, pickles, onions, cheese, sauce. I don't know. That's probably good enough, right? So that's two. Oh, then we want some text input. So let's do a name for the order. That's just like a string input. Any other styles we should add or any other cool features that you want to show off that we should include in this list?
John: I think I'm happy with this so far.
Jason: Okay. So the part that I get is I get this .arg. I see we're using that arg import. So I can say new and then I just put in, like, toppings.
Jason: Okay. So at this point, I'm lost. So what are these next pieces here? There's short, there's long, there's value name. Then I see some stuff about being required or taking multiple.
John: Yeah, so for short and long, I don't know that we need to necessarily worry about that. That's like if you have a command line, you know, for example if you have NPM install-d versus dash, dash, save. That's a way to handle short and long types of flags.
Jason: I'm going to start with style. Okay. So we don't need short and long. Value name, I assume, is like what shows up in the program for use.
Jason: So we would give it like burger style. And then about would be what type of burger do you want? Then takes value, is that like an option thing?
John: That's a good question. I don't know that that's -- oh, yeah, I think what we want is -- I do think we want that to be true. I think it takes value versus not is a flag versus something that takes an input value.
Jason: Oh, I understand. Okay. So this would be if we don't set takes value to true, then it would be like setting like -V is for the version. You just say V and that shows the version. It's like a boolean flag.
John: I believe so, yeah.
Jason: Oh, I got you. Well, maybe we can just start here and see if this does what we want. We have our matches. We're setting up the program. We're taking our first argument, which is style. Then we have a value that's going to come out. We have a description for it. So right now I believe it's going to be just an arbitrary kind of -- you drop it in. Then it looks like at the end we run get matches.
John: Yeah, I think you might need to close your -- here, I'll do it. I forgot I could do it. Close this here. Wait.
Jason: Oh, I know what I did. I didn't realize that arg had to be open like that.
John: Right, because it takes an arg.
Jason: Gotcha, gotcha. It doesn't like my FN. There we go.
John: Semicolon. Yeah, that's where I mentioned the compiler gives you some nice things.
Jason: Nice, okay. So then this would -- actually, let's just run this and see what happens. I'm going to do cargo run.
John: I'm in the sure it's going to do much. It's compiling -- it's bringing in that library and compiling things.
Jason: It should do probably nothing, right? Because it's not like blocking on this. We're not building -- this isn't like one of those CLIs where it's going to ask us a series of questions. This is like we just pass flags.
Jason: Okay. Oh, because we didn't ever use it. Okay. That make sense.
John: So that's what I was mentioning about it being kind of kind to you when you do something wrong. Hey, if you meant to do this.
Jason: Yeah, that's actually really -- I mean, this is great. This wasn't just like your compile failed on line nine. It's like, hey, fix it like this, you doofus, but lovingly.
Jason: Levinson, yes, co-pilot is giving me all the help. That's where the suggestions are coming from. So then if I want to cargo run this, I would just do style and smash. Theoretically, that would run it as if it was the command line argument.
John: You might need another dash, dash after the run. That way it passes the flags. Yeah, like that.
Jason: So what this means, to make sure I understand it correctly, we're running cargo run, and if we didn't add this, these would be flags passed to cargo run.
Jason: So instead what we're doing is we're saying cargo run with no flags and then to the script that's being run, because we're skipping in, run these flags.
John: Right, exactly. So it's passing. You might see something similar when you're doing particular NPM things or you want to pass like a flag to Node instead of directly to NPM. It's a way to pass arguments to the binary instead of to the thing that you're running.
Jason: Okay. So I did break it. And I'm not sure what I did.
John: Argument style. Oh. You might actually need the long thing I told you that you didn't need.
Jason: Oh, okay. I do long style. Thanks, co-pilot. Run it again. And this time it did run. So we still get that warning about matches. But that's also really cool. If I run style and then this is not real, it's going to fail. Hey, this doesn't exist. So now we also know whether or not we've appropriately registered these flags.
John: Right, and also, you may have noticed there at the end it automatically creates like a help for you.
Jason: That's really nice.
John: So it'll tell you like what arguments and the builder -- it'll build it for you and tell you what you can do. That's where a lot of the about text comes in and the value name and everything.
Jason: Yeah. Well, that's super handy. Like, this is -- okay. So we've gotten to the point where we can put data in. As we can see, nothing actually happens here. So if we want to display that, what do we do next?
John: So now we have to actually parse out. So we created that matches variable. We actually have to parse that out. So what you might see in the readme there is you have to I think wrap. So you can't just interact with something. What happens with matches, in Rust, it's what's called optional. So if you've ever written Swift, it kind of looks like. I know a lot of Rust people will yell at me if I say Rust looks a lot like Swift in a lot of ways. Not yell. They'll kindly say, yes, I know. So what we're doing here is the compiler is asking us to explicitly handle the fact that this thing -- so matches.value of either returns none or returns something. We have to unwrap that.
Jason: Look at this. It's amazing. So it figured out that. If I run it like this, and then let's run it appropriately with a flag that exists, theoretically speaking it should now print out you want a smash burger. I got something wrong.
John: I wonder if you need -- oh, if you need value of to be just style. I think it might actually be the name of the flag. Let me see.
Jason: Okay. Let's try that. Yes. Okay. So that's interesting. So this is only for docs. This is what you use in the program, and this is what displays in the help. So if I run a bad one, we can see -- so this is what shows up in the help text. But it looks like that's not actually what shows up in the code. It's style that shows up.
John: Got it.
Jason: Okay. That makes sense. I get that. So then let's do something else. I want to have a guard for valid values. So is that something that's built into clap, or is that something that I would just write some logic for here? Like if the value is one of these allowed values.
John: I think you can do both. There should be -- so, where's the -- I just did this. There's pattern matching.
Jason: Oh, is it -- this is pattern matching here?
John: Yeah. So it may look kind of like a switch statement.
Jason: So instead of doing the if, we would do matches --
John: So inside of the if. The if is unwrapping.
Jason: Got it, got it, got it. Okay. So let's do match -- or this would just be I, right? We already have access to it here. Then -- come on. Co-pilot, you cheeky -- then the default is the underscore. That's the bottom here.
John: Yeah. Preferably not with ableist language like this documentation has. But you know.
Jason: Yeah, yeah. Wait, no.
John: Yeah, that syntax. The funky print line syntax in Rust is a macro. So it's not quite a function. It's something different, but the print line, you'll see macros suffixed with the exclamation mark. Then inside, you can see the string interpolation happening.
John: It'll be interesting to see how it handles two words, if it's a command line.
Jason: I have a guess I need to do it like this. Yeah, because if I do it without the quotes, I think it's going to treat it as multiple arguments. Yeah, so with the quotes. Perfect.
Jason: Okay. So this is an extremely judgmental command line interface. This is amazing. I love this. Already I can see how much I just like Rust. I liked it when Prince showed me. I like it when people show me how they're building stuff in Rust. Seeing this, I'm just like, man, I just want to build more Rust stuff. So let's do some toppings, right. Toppings are going to be kind of the same general setup here. So I think I can just kind of copy/paste this whole thing. But what I want with toppings is I do want to include a short. We'll do short of t. Get out of here, co-pilot. You can't possibly know that.
John: I can't wait -- well, I guess my code is not on GitHub. It's not going to randomly start copying and pasting my code into here.
Jason: So what toppings do you want on your burger? Then each one of these is going to be a topping, but I want it to be able to select multiple. What did I do wrong?
John: Why is that yelling at you?
Jason: Are you just confused?
John: No, I think it's -- so this is where we run into some of the memory management things in Rust. Because of the way strings work, you know, strings a lot of times will end up on the heap. Because we don't know how big they're going to be and they have a string pointer and there's a few different types.
Jason: I just looked, and they single quote it. That made it go away. Now I have questions. If this is too esoteric, feel free -- like, I can go do my own research. Why is that make a difference? Single quotes and double quotes are different in Rust?
John: Right. So the type that this function expects, I think Chris mentioned in chat, it's a character versus a string. So it's --
Jason: Okay. So they're not the same thing.
John: Strings and characters are treated as different types.
Jason: Okay. So what we're seeing is if it's a single quote, you get one character. If it's a string, like multiple characters, you use double quotes. And if you try to use double quotes for a single character and it's expecting a character, it will yell at you.
John: Right. So I think, you know, the way that this library was written, the short function expects explicitly a character, which is why we're seeing the statically typed, you know, compiled language thing. I guess if you're using TypeScript, if you try to pass a number into something, it expects a string, it's just going to yell at you.
Jason: Sure, yeah. Okay. There's some comments. Oscar Allen says the string versus and string. It's ownership, so like everything is a scope in programming.
Jason: You can -- everything is owned by one scope in Rust, but you can, like, let another scope borrow it. That's what the ampersand is.
John: So with the quoted string like that, it's a string literal. We know exactly how long it's going to be. I think for the ampersand string -- yeah, exactly. The best advice is don't worry about it yet. Let the compiler yell at you when you use the wrong one and Google how to convert to the other. But exactly, it's a pointer to the string itself. So if we have a string that we don't know how big it's going to be, it could be dynamically generated. That's when I think we get into talking about stacks and heaps and how memory is allocated so that we know exactly what memory we need to hold on to.
Jason: Right. So I'm going to take Oscar Allan's advice and ignore that. You started staying that stuff, and I got sweaty. (Laughter) But okay, so now we can do one topping, right. So let's try it. I'm going to do -- we want a smash burger, and for the topping, I want, let's say, lettuce. Right. Could not compile. Why couldn't you compile? Oh, wait, what did I do? I didn't save. Okay. So it says a smash burger, right. We should change this. Asked for a smash burger. So that's good. Then down here we want to do -- I'm just going to copy/paste this whole block because we're going to do some changes on the toppings, which are -- yep, toppings. And we want topping to be -- I have a plan on what I'm trying to do here. So my thought is we want toppings to be an array of input. So my thinking is we'll do like a print, and we'll just do toppings so I don't have to figure out how to make this work. Then for the match, we'll put in our -- like the available toppings. Then we can just print -- if I just want to print the value, do I just put like the i in here? Or do I need to do the string with the curly braces and pass it as an argument?
John: I think the compiler is going to yell at you if you try to just pass it in as an argument. So what you can do is do an empty string with an empty curly brace inside and pass it in.
Jason: Right. Okay. So for these, we would do -- I'm trying to think. What are the available -- lettuce, pickles, we'll copy/paste this.
Jason: You're putting pineapple on a burger? What are you, an anarchist?
John: I mean.
John: Do you want an honest answer?
Jason: Tomato, onion. That's good enough. We'll stick with these. Then if you don't -- let's see. We don't have -- sorry. So if we don't have something you want, we'll just -- let me fix this formatting because I think it's going to -- it should fit, at least. Yeah. All right. It doesn't like this because I forgot to add a semicolon. So this, then, should mean that we get each of our toppings, and it shows us what we picked. Oh, you know what I'm going to do, I'm going to make these into like a bulleted list. So now if I run with lettuce, it should say you picked a smash burger, toppings are lettuce. But then I want to do like lettuce and onion. It starts to break on me because it was provided more than once but cannot be used multiple times. Now I'm trying to figure out what do we do to work around that.
John: Right. I think it looks like we have the ability to use the multiple occurrences.
Jason: So I just drop in .multipleoccurrences.
John: The example they give is for a verbosity flag. If you do a lot of commands, there's different levels of verbosity.
John: I don't know how to pull out all the different values as an array. There's got to be a function in here somewhere. Let's see if the auto complete will let us.
Jason: Let's see. So the value of style, would we just need to -- would we just need to maybe -- I'm going to try something and see if I'm understanding how to debug Rust. I'm going to attempt to print out the matches value of toppings. Oh, my goodness. Co-pilot, stop it.
John: You know, I could just take a nap.
Jason: (Laughter) So I'm doing it wrong. Cannot be formatted with the default formatter.
John: So it's an option, like we mentioned, which means it may be null.
Jason: Oh, so I got to do this thingy?
John: So inside you can just try to print the value of i. Inside of the if.
Jason: So my thinking is -- what I'm hoping is that clap is going to give us back like an array, and that array might be empty. Is that -- I mean, maybe I'm incorrect.
John: We can try it. It looks like it's giving you back an option that's a string, or a string pointer, I guess.
Jason: Okay. So let's see what we get back here. We printed, and it says some lettuce, which means it didn't pick up the second argument?
John: It might just grab the first value. Let me see what -- let me look in the code for clap. I'm guessing there's a separate function we can call that pulls out all the different --
Jason: Got it, got it, got it.
John: I'm also wondering if we type matches. And see if it auto completes.
Jason: What a great idea. Let the tools do the work. Values of?
John: That might.
Jason: Let's see what happens. Nope, not that one. Here, this one. Try it again. And it shows us -- no, it doesn't like that. And it didn't like it because --
John: Oh, the debug is not implemented.
Jason: So does that mean I need to do something else?
John: Let's see what values gives us. So values is a strut. Let's see. Implements iterator. So we can just iterate over it.
Jason: Oh, I did screw that up. That's true.
John: You can do something like -- the cool part is, yeah, you can control click into the actual source code of the library, and it'll pass -- like it'll bring you into -- where did I go? There I am.
Jason: I love live share. Such a good feature. Okay. So let's talk through what just happened here.
John: Yeah. This is kind of nasty Rust right now. You can see the unwrap is going to -- let's see. So we know what matches.values of is going to give us. Oh, my gosh. That's not working.
Jason: I can just drop vals right in here. That looks like it's going to work. Let's try it.
John: Let's see.
John: So, yeah. There's a little bit of nastiness we're doing here.
Jason: So let's just step through it and kind of work through what's happening in our heads. So we've got our matches. We've got our values of. So the values of is a struct. A struct is, to use a poor analogy, kind of an object.
John: Yeah, so it's an object with a defined set of fields. If you think in analog and TypeScript, it would be a type. It has a defined interface of things that it accepts, and it has behavior that comes along with it.
John: And this is a struct. So I think Chris mentioned it in chat, that debug printing relies on a trait called debug, which I think just about a lot of, if not almost every built-in type in rust implements the debug trait. Basically, if you're thinking in terms of -- you know, think back to Java. If you implement the two-string method, it will -- whoa, my brain just rebooted.
Jason: No worries.
John: You can implement traits to give yourself built-in functionality. But here we have values struct that implements the iterator trait, so it can be iterated. So what we're doing here is we're doing .unwrap because we have an optional. And .unwrap, if you have an optional, it's the equivalent of give me this thing but then crash the program and panic. So Rust has two different types of main errors. They don't really do exception handling. They do recoverable errors and unrecoverable errors. So unrecoverable errors will crash the program or panic. And there are recoverable errors, which the compiler forces you to handle. So here, we see the safe way is the if let some below and only executing the code inside the if, if the thing is not null. If it's actually present. But unwrap is basically a way to force it to not be null but then panic if it is missing. It's kind of one of those things we talked about where you can do things in somewhat of a less ideal way if you're trying to hack something together quickly.
Jason: Sure. And so to kind of repeat this back and make sure I understand it, by default what we've done is we've put this into a some structure, which means that some is like -- I'm not going to say the word. It's basically like a promise. The promise might be rejected or it might be fulfilled, but it's always a promise. So you can depend on that your application and react based on whether or not it has a certain thing. So a some might be empty. You don't have anything in this collection. Or it can have something, in which case you can do something about that. But when we unwrap that, we're basically saying just give me whatever is inside this thing, and I'll deal with it.
John: Sort of.
Jason: I was like, oh, I nailed this. (Laughter)
John: More or less, yeah. It's not going to handle like empty arrays or anything. So if an option -- it either has a value or it doesn't. So a some is a way to safely say only run this code if something is in here. And then an unwrap is -- I'm trying to think of a way to -- like an analog to this. You're not null checking the thing before you run it. You're saying unwrap this thing into whatever its value is. So optional or option is an enumeration. Exactly, it's an ENUM. It's either something with some or none. If this thing is a some, give me the value inside it. That unwrap is saying, you know, I'm expecting this thing to always be a some with a value inside it. In the case that were -- yeah, if nothing happens, or if nothing is there, then the program will crash at that point, versus the program will do nothing if there's nothing in it, but it won't crash.
Jason: Gotcha. That makes sense. So now that we have these values, I think we need to rewrite this a little bit. Instead of doing the if let, we need to just loop, right? We need to just kind of for each. But I'm not quite sure what the equivalent of that is in Rust.
John: So what we see here is the .collect takes that iterator. We don't necessarily need to collect it. Let me get some of the syntax.
Jason: Nice. But then we can -- oh, you know what we could actually do. We can just move this right up here, and we can say we won't actually try to pull the values out unless we're sure something is there.
Jason: Then we can move our match inside of this loop. That should -- let's do this. Wait, how about I do this. Boom, boom, boom, boom.
John: We can do this. I don't even know if we necessarily need to collect. Collect is taking the iterable thing into something that's similar to an array.
Jason: Wait, did I just break this whole thing? Do we need to pull it out?
John: Matches the values of. That's what we want. So that's giving us the values. And now we can do that. That should work. Yeah, that should be fine.
Jason: There it goes. We did it. Now I can drop out this part where we're doing a print. Try it again. All right. Let's try it with something else. Let's add pineapple, like the chaotic evil we are. Oh, wait, I did the wrong thing. Excellent, okay. This is great. I'm like super happy about this. So let's do something like -- let's add one that we don't have. We'll add pumpkin. Nice. I love it. That's so funny. I love this. I'm so happy about this. This feels like it's all coming together. It doesn't feel super mysterious. There's a little bit of foundational Rust that I need to learn so things like this don't feel quite so like what is going on. But this is the sort of thing you would learn from watching the "What is Rust" episode with Prince. And I'll do another shoutout to Rustlings, which is good at introducing you to these.
John: And there's a lot of ergonomic stuff you can do. This is started to get pretty nested here. There's a lot of helper functions and things like that on types and collections, which is where traits really become fantastic. Essentially, it's compositional behavior. As long as you're -- for example, your values struct implements the iterable interface, it's sort of like implementing an interface in Java. You get a lot of behavior for free out of the language that you don't have to do necessarily the most verbose way. I want to say if you even want to do, like, matches, you don't have to do an if let here. You can pass in defaults on things like that. It lets you kind of code in the way that your brain thinks. Like if you think, you know, this is straightforward to read, then you can keep it this way. If you want to write it slightly differently, then you can do that.
Jason: Well, you know, we've got 25 minutes here. I guess probably 20 minutes left. So why don't we do a little refactor here. I'm seeing an opportunity, which is that we have a couple other things that we would probably want to include. If this was an ordering app, we would want -- you know, we'd have our toppings, then we'd also want to do another option. We can select delivery or pickup. Then we'd want another option. You know what I mean? So there's a lot of things that we would want to do. So I can see this starting to get pretty repetitive. Maybe it does make sense to kind of abstract this out a little bit. If you were going to do that, where would you start?
John: Um, so let's think about kind of -- I guess let me scroll up a little bit.
Jason: Yeah, let me go find you.
John: I think if we have a required if, I'm not sure I follow. Oh, if you have one flag, you're required to do something else? Is that what I'm seeing in the chat here?
Jason: Oh, yeah, Jacob, can you clarify that? Required if condition is met? So if you say you want lettuce, you have to specify between iceberg or romaine, that kind of thing? If delivery, give address.
John: That's exactly what I was going to say, if you have delivery, then give an address.
Jason: All right. Actually, you know what, that sounds like more fun than refactoring code, and it's something we can learn together. So let's do order type and just skip the short. We'll instead say -- let me unpin because that's going to jump around otherwise. We'll do order type. And we don't want multiple occurrences.
John: Simultaneously add the address here.
Jason: Nice, okay.
John: Oops. That was the wrong keyboard shortcut.
Jason: Okay. So we've got -- you can choose dine-in, pickup, or delivery. Then it'll ask -- this one we only want to ask for delivery. So to do that, we need to -- oh, did it change my order type? That's cool. It like reformatted that for me. Here you go, doofus, you can't do that. So we know that we need an order type. So then if I go and run this right now -- well, I guess running it would do nothing because we haven't set anything up yet. But we can set down here -- let's go with this one. Actually, we can just copy/paste this whole block. Let's copy/paste this. Go below this if block. We'll say order type, and if the order type is delivery, then we need to do something. Otherwise, we can just -- you ordered for delivery. Or you ordered for pickup or dine in, right. We could make that more restrictive, like if you said something that wasn't in the list. Obviously we could pattern match that here and do that sort of thing. But this should be good enough. So here, this now is something that I haven't tried yet, which is we want to do additional logic based on this, and we also haven't checked if the delivery address is set sort of thing. So is this something that clap handles for us? Are we kind of writing logic on our own here?
John: I don't see built-in logic for it. They have subcommands, where like if you did --
Jason: Oh, wait. I apparently scrolled to exactly the right place. Requirement rules can be required only if certain arguments are present. Good. Good. Okay.
John: How do we do that?
Jason: How do we do that? Requirement rules.
John: Where are we at?
Jason: Documentation. I'm going to click this button. Is there like a required -- required true. Arg with name. No, we already said --
John: I wonder if their examples have something.
Jason: What if it just autocompletes for us? Does that do anything? Required if -- requires if.
John: Oh, there we go. Jacob just posted. Oh, I just clicked the wrong button.
Jason: So we've got Jacob to the rescue. Required if. So required if other arg value. Oh, perfect. That's exactly what we want. We're going to do required if, and it would be order type equals delivery. And it doesn't like this because -- don't you lie to me. You showed me that. Required if equal. I wonder if they rewrote this.
John: Let's see.
Jason: This is interesting. These ones are like with name instead of new.
John: Yeah, I think that's a different version. Oh, we're on the version 3 beta. Let's see, clap. Let's see.
Jason: Can you just grab different versions on here?
John: Yeah, there should be the little box or the crate at the top. Where it says clap 2.
Jason: Got it, got it. Just need to make the window bigger. Makes this easier to navigate. So they just changed it in 3.0 to make it a little more clear. That is pretty clear. Required if equal. If the other input is whatever. So now if I try to run this, I will say -- and, oh, yeah, we need to set this one to be required in general. So required, true.
John: Side note, docs.rs is fantastic.
Jason: This is amazing. I love this whole setup.
John: You can search any crate.
Jason: I like the consistency of it, too. The fact that the docs are like -- so these, I assume, are generated, right? I guess they're not really. These look like they're written by a human.
John: I imagine there's some degree of structure to it.
Jason: A bit of both.
Jason: Okay. So now that I've set this, I'm going to run it. Let's go -- we'll simplify this down a little bit. We're going to do style smash. We'll give it a topping of lettuce, and it should yell at me. Yes, you need a required argument of order type. Great. So let's do order type. We'll do pickup. You ordered for pickup, great. Now let's order for delivery. And it says, nope, you got to do an address. That's amazing. Yup. Okay. So then we would down here, order type. So we would need -- how would we do this? What I need to do in here is if we're in delivery, can I do a little bit of logic? What I want to do here is if it's delivery, I want to then get the value of address with this same kind of logic and print it will be delivered to and show the address.
John: What I would typically do is write that as its own separate function. You can call that function inside that line, that way it doesn't start to get unwieldy.
Jason: That works, yeah. Let's do it. So I have -- let's see. We'll do get address. Is it an argument?
John: Yeah, what do we want to pass in here?
Jason: I'm going to pass in, I assume, either the matches or -- actually, why don't we just pass in the matches so we can do easier math. That means this is -- can I just write arg matches and it's going to know what that means?
John: You might need to preface it. Oh, you might need to put it at the very top. Why do I keep saying this if I know I have live share and I can do it myself? Let's see. We can put it up in the thing at the top, the import. The use statement.
John: What was it called? Arg matches.
Jason: So I'm making some big assumptions here because I don't really know what any of this means. My function is to get arg matches. Then it doesn't like -- it says it's never used. That's fine. So then in here, I would be able to do -- let's do here. We want address. If there is one of those, we would be able to print. We would want to include if you ordered for delivery -- which, this is messy. We would fix this because it's not great right now. But it should get us where we want to go. We don't even need to -- we know if we're in here --
John: I think that's not going to be happy with you. Oh, yeah, that's fine.
Jason: This should work, I think. So we order for delivery. Then the delivery address is going to be whatever gets entered here. So then I can -- if we are delivery, instead of this, I should be able to do get address and pass in matches. I think it just works. So let's try it. Nice.
Jason: That's dope. If I change this out and we say pickup, that's cool. That is very cool. I dig this. Like, this is super fun. I really, really like the feel of working with Rust. It's very clearly well-considered. Like, the language design is good. I like this a lot. Okay. So we have maybe five minutes left. If that's not enough time, we can also just like kick to some further resources and call this one a success. Is there anything else you want to show before we wrap?
John: Um, not really. I mean, this has been -- yeah, this has been great. I think I could case for if you want to learn how to use Rust or you want an excuse to use it more and you don't want to go the traditional kind of, you know, clone some other data from an API and build up a service. Like, a command app line is a great way to do it. Find yourself doing something off, if you normally write a Python script and you want to play with Rust, it's really fast, like we've seen here. It gives you a lot of type guarantees. It gives you a lot of help as you go along. Someone mentioned it earlier in the chat. What were they saying? The compiler really -- it's almost like, not to use the same term, but a co-pilot. It's there with you, and if you do something you're not supposed to, things like I need a string and not a string pointer, it'll tell you. It's my favorite way of learning something new. Building something, doing it wrong, and having something tell me I'm doing it wrong and maybe help me with how to fix it.
Jason: Nice, yeah. So we built this thing on, let's see here -- so we built it on 0.1.0. We're using cargo version 1.53.0. I'm not sure if that's the latest or not. I think we're using Rustc1.53.0. I don't know if these are in lockstep or not. This is really powerful stuff. It doesn't feel like too much. It feels like something I can do. The docs are amazing. All right. So for somebody who wants to learn more, what would you recommend they go and look at? Bearing in mind we've looked at Rustlings and we know docs.rs is a thing and we can look up different crates.
Jason: Yeah. I'll do a quick shout out to Chris Biscardi, who's been in the chat with us today. That's not the website. What am I doing? Geez. Chris has a whole bunch of Rust material. It's all very good. You should go and check it out. So make sure you go look at that. I know he's been building some games. He's been working on foundational stuff. Does a lot of kind of learning in public and sharing of that as well. So make sure you check out Chris' stuff. Chris, also let me know when I said I was building on Rust CLI version 0.1.0, I wasn't. My app is called Rust CLI. Its version is 0.1.0. I'm a doofus. (Laughter) Sorry. So with that being said, John, thank you so much for hanging out. Everybody, make sure you give John a follow on Twitter. It's a lot of fun information. Yeah, so we can also -- you can get live captioning of this show and every show on the homepage of learnwithjason.dev. It is done by Rachel from White Coat Captioning. She's with us today writing down all the silly things I say. And that is made possible by Netlify, Fauna, Auth0, and Hasura, who all sponsor the show and make it a little more accessible, which I deeply appreciate. Make sure, while you're checking out the site, you check out the schedule. We have so many amazing things coming up. We have -- let's see. What else? What's happening? Next week I'm out. Next week I'm going to a cabin in the woods, and I'm going to have no internet and it's going to be amazing. The week after that, we're going to have Daniel Phiri on the show. We'll have Tomasz teaching us about TypeScript. We have Srushtika teaching us about Jamstack. I'm really excited about that one. And then right-to-left support. Please come and check this one out. It sounds like it's simple, and it's really not. Come and learn about all the things that can make the world hard for the billion-plus people who use right-to-left for their writing language. With that, I think we've got ourselves a successful episode. John, thank you again for hanging out. Any parting words for us?
John: No. Thanks. This has been great.
Jason: All right. Well, thank you, chat. Y'all come back now, but for now, let's go find somebody to raid. And we'll see you next time. Thanks, everybody.