This video demonstrates manual CLI argument parsing in Rust using env::args() and env::args_os() for handling file paths, along with async file reading using Tokio's async runtime. The presenter explains how to collect command-line arguments into a vector, use Option types for safe index access, convert OS strings to PathBuf types, and implement async file reading with proper error handling using Result types and the Box<dyn Error> pattern for error types.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Manual Coding in Rust
Added:Hi, I'm David the audio developer on duty. In the past months, I have programmed a lot with AI and I'm afraid that I lose my skills. So, today we're going to have a look at Rust and get back into manual programming.
First off, why Rust? Why not any other programming language?
In my opinion, there will be a lot of AI-paired programming in the future for everybody. At least for those who can afford it.
That means we look less and less at the outputted code, but we want to have safe guarantees. And I do think that Rust can help you quite a lot.
It's very pedantic in terms of safety.
So, it doesn't allow you to do things which other languages do allow you to do.
With a result that you can trust the outputted code a bit more than in other languages, let's say like JavaScript.
Rust is a very complicated language in the sense that you have to learn a lot of concepts for you to write it in a proper way.
And many things which are handled automatically in other languages such as Go or TypeScript are manual effort in Rust.
Now, with the advent of LLMs, that becomes not the bottleneck anymore.
So, that problem I would not say vanished, but became way less severe.
That's why I think thinking more on the outputted code instead of writing the code, Rust has a great future for all LLM-generated code. Or in other words, Rust will be very useful for LLM-generated code, but to be efficient with this approach, you have to learn a lot about Rust because you, as a programmer, need to review the generated code and you have to understand the underlying concepts at least to some extent. In the past months, I almost haven't written any single line of code manually.
And that concerns me because I have a feeling that I'm slowly losing my ability to write code and I rely too much on machines which are not under my control, which are governed by external entities.
Now, I do not think that LLMs go away or that code generation goes away.
We are far from that.
But, I want to have at least a little bit more of ownership of how code is being produced.
Hence, in this video and maybe the following videos, I'm going to go full manual and I write the code all by myself. The goal is not the outputted program, but rather the journey to learn more concepts and to go out of it knowing more than I knew before. This is my goal. It will be slower than with AI.
It will be less standardized as with AI and probably even worse than with AI.
But, at least I get to know a lot of things. The project I am going to write is relatively simple. It's a CLI tool based on Markdown and I want the user to be able to define certain sections of Markdown and then each section represents a certain thing. And the thing can, for example, be a GitHub issue.
And the way I want to design it is that whatever you put in a in the content of a section will then be linked and synced with a GitHub issue or some other things. Maybe I'll make it extensible, I don't know yet.
So that you can modify the content in your text file, hit a button, and be sure that the respective comment or issue or whatever gets updated automatically. So, let's get started. I call it MDLink.
And I run cargo in it. Now, this created a certain structure, and I can now edit main.rs and see that it prints hello world.
And just to see that it works, I run cargo run.
And it should print hello world, which it does. So, everything's bootstrapped bootstrapped properly. So, first, I want to collect the command line arguments, and there's this env::args functionality, which gives me, I think, an iterator.
And then when I call collect, I can make it a vector of string.
So, let's do that. All right. So, let's get started. I run use standard env.
And here I can say let args equal env:: args. So, this is the function, and now I run collect.
And I think this will not compile, so let's try it. Cargo build.
And you can see that uh the type must be known at this point, because if I just do this, um it cannot satisfy um the type resolution.
And it tells me here required by this bound in iterator::collect. So, if I run iterator::collect, then I have to provide this type information, so the Rust compiler knows what that is. The error message here is re- relatively nice, I would say. So, let's also just for the sake of it run this command here.
And this gives probably a bit more information.
So, here's an erroneous code example.
So, I have this string hello.
I I think get an iterator over the characters and reverse it and collect it. And now, what is X?
So, the type cannot be resolved.
So, the type inference did not work. And I have to specify this generic type parameter manually.
So, this is a collect method on the iterator trait.
And let's see what we need to do. So, in this case, we need to provide the type manually by writing vec character in this case.
All right. So, let's do this. I write vec and this time it's not a character, but rather a string.
And now, if I run this again, it can compile. That's great.
Now, I want to print this output here and for this, I can write debug just to see that it works and I run cargo run.
And yeah, seems to work properly. So, I have the proper args. And just for the sake of it, if I run it with foo, now I get also foo.
The reason why I do it is of course the first argument here in this resulting binary shall be the path to the file.
So, now in Rust, I want the second element and I have a vector, so I look at the documentation for vec. And it has this nice method called get. And the nice thing about this method is that it returns an option. Meaning if I get a certain index which does not exist, for example here, um I I run get three, but the maximum index is two, then I get none back. And uh this is nice, and this is what I want to do.
So, let's see if that works. So, I write let Now, I have the path, which is args get the second one.
And now I can also debug it. So, let's see if that works.
So, let me first build it, and now I run it with foo.
So, I get path none.
And it should not be a two, it should be a one, because it's the second argument. And now I get some foo.
And if I don't provide it, then I get path none, which is great, exactly what I need. On the option type, there is a expect method.
And here I can provide an error message in case um I get none.
So, I can say first argument must be a path to the markdown file.
So, let's see if that works. I run cargo run foo.
Everything's fine.
And if I run cargo run, I get the error first argument must be a path to the markdown file. So, what I don't fully like about this code is this collect here, because I don't want to collect everything into a vector, because it's a bit unnecessary. It's not a big problem for this tiny program, but there's a better way to do it, and I think it's called the nth method here in this iterator, and I can say nth the first one, and then I expect that the path is a file, and this is not a vector of string anymore, but rather the path itself.
So, let's see if that also works.
Yes, so this is okay, and this is also okay.
So, we're going to go with that. Now, there's still a tiny problem with this.
I use here env args, and I read online that env args will parse it as UTF-8, but some file paths might not be valid UTF-8, and for that I shall use args OS to handle those cases. So, let's do that. Instead of args, we write args OS, and now we see if it still works.
So, let me also format it, and run it.
So, that's still is fine, and it's still probably parsed, which is exactly what we need. There's still something which we can improve here, because in the end I want to have a valid path, and not just some kind of string.
Makes it a bit better with respect to maybe some helper methods we need later on.
And in this argument parsing Rust Cookbook, they mention here this path buff, and that gets used in order to transform whatever argument comes in from args OS into a path. So, let's do that also here.
So, path is part of standard path and then path path. So, let's use that. Use standard path path path.
And now we can say this path here is a path path.
But if I do this and not anything else, then this will result in an error. So, let's see that.
So, and args OS and then some expect um I expect here path path, but I found an OS string. So, what needs to happen is some kind of conversion between OS string and path path. And the way this works is um this into method. So, if and the compiler is very helpful here, if I add this into then I can convert this OS string into a path path. It's very helpful error message.
So, let's try this.
.into And now if I compile it again it works and if I run it then it's correctly printed. So, there are a lot of things I have to look up with respect to argument parsing, path handling, etc. And this is because I've never used Rust professionally. So, I'm learning on the go and it's been some years since I delved into Rust. So, I have to refresh my memory, of course, and learn all the concepts again. And what I like about this Rust cookbook is this CLI args struct.
It's absolute overkill to use this in our example because we have a single property which is the file path and I would never introduce such a thing in such programs, such simple programs, but maybe we will use other argument flags later on.
So, maybe we can already go into this struct architecture where we have this nice struct which parses all the command line arguments in a nice way.
And of course, there's also clap which is a very popular crate in the Rust ecosystem which does all of that yeah, almost magically where you in principle which just declaratively or with the help of such methods tell what the program needs and then everything is generated automatically like even the help etc. We're not going to do that for now, maybe later, but let's do the manual way because we want to learn. So, I will write a struct CLI args.
And it will have a path which is of type standard path path path.
And that's about it.
Let's just see if it still compiles.
It does.
All right. And now we want to move the information from those arguments into the CLI struct. All right. And since I imported path path directly, I can also just write it like this.
Now, I want to have one method on CLI args, static method which constructs such a thing.
So, I write impl CLI args.
And I want one function called parse same as in the example here. There's this one function parse and this will based on the environment derive the respective information and then return this struct where the content is filled is filled with the information.
So, parse will return self.
So, I will return CLI args with the path filled.
So, I write here path and now we just have to get the path again and we have that information already here.
So, we can just put it in.
And hopefully that will work.
So, let's just say let CLI args equals to CLI args parse.
And let's see if it builds. Cargo build.
Yes, it does. Cargo run.
And okay, I get the error which is exactly what we need. And now if I debug CLI args.path then if I run it with foo I will get foo. And as a tiny nitpick it's snake case to be make it consistent with the rest of the Rust ecosystem. All right, now that we have the path, I also want to read the file. And I want this whole program to be async because the main goal of it is to sync the content of each markdown section with some remote thing, for example, a GitHub issue. And that syncing is, of course, asynchronous.
And I want the whole thing to be asynchronous and consistent. So, we will use Tokio to as an async runtime.
So, cargo at Tokyo.
And now we have Tokyo.
And the documentation of Tokyo also gives us an example on how to read the file. So, we need Tokyo file system.
And then the async function, which we already have.
And then just be um yeah.
And then with this uh Tokyo.dot.main thing.
And then this whole function can become async.
And allows us to call asynchronous operations like this read operation and use await there.
So, this will return a future.
And if we await it then and use this question mark to unwrap the potential error, then we get the actual file contents back in a truly async fashion.
That's what we're going to do now. Okay, so let's go through it step by step.
First, we have to add this attribute.
And in Tokyo to make a function or the main function async or asyncable, so to say, you need to add this attribute here, Tokyo main.
So, let's do this.
And now there's an async runtime.
And this allows us to write here async function main instead of sync function main.
Now, this will not compile as shown here because this main function is not allowed to be async.
And uh yeah, the error message is not so great uh because the reason is we we're just missing this return type here.
So, let's look at this a bit more in detail. So, the return type is a result.
Okay, that's easy. Result type can have two um yeah, combinations. Either an okay type and an error type. That's why there are two generic arguments to it.
So, that is fine. The first one is the okay type which is an empty tuple which is uh returned when everything is fine. So, this is understandable. And the other one is a bit more cumbersome to read.
And uh let's digest it step by step.
So, first this function here needs to return a result type.
And here there are two arguments. The first one is the okay type and I can return that and um we just say that we return an empty tuple if everything is fine.
So, this is good. This is handled. And the other one, yeah, seems a bit more complicated because there's this box thingy and there's din and there's a trait. So, this is not a type but a trait.
And also here this static lifetime.
So, let's do it step by step.
What happens if I just run standard error error, right? So, one would be inclined to say, "Okay, I don't care.
I'll just give me an error." So, let's see what happens if I compile it.
So, um it The error message is here more or less helpful because it says uh it expected a type in this position here but it found a trait.
And now we have to convert a trait into a type and we can do that with the done keyword, which is exactly what we need.
So, we want to have some type which implements this error trait.
But, when we write done, that's not enough and if I compile it now, then there are other errors which uh happen because uh this does not have a size which is known at compile time and we have to fix that.
And to fix that, we can just hide it behind a pointer and this is what box does. Box is a pointer to something which is owned by it. Okay?
So, let's just write it here. Box and we just add this dynamic thingy here inside and the big advantage of box is that the size is the size of a pointer. So, it's known at compile time.
So, we don't have this problem which we just had before.
So, what happens if I build it?
Now, it says main is not allowed to be async.
And uh yeah, so it expected a result but found a empty tuple.
And okay, yeah. The reason for this is I actually want to return this. So, this semicolon needs to be removed and actually the compiler is extremely helpful with this.
Okay. So, let's continue.
Yeah. So, now the error messages are not helpful anymore because it just said uh says main function is not allowed to be async. The reason why it didn't work is I have to add additional Tokyo features.
So, I have to write the cargo at Tokyo and then dash dash features full.
Only then I will get this Tokyo main attribute and then the whole thing can compile. So, if I write cargo build, it compiles and I can also run cargo run which is exactly as we expected. So, now we want to actually read it. So, we use Tokyo FS and then we can write FS with this read function and provide the path to the file. So, let's try Tokyo FS.
Use Tokyo FS and now we write let content or let's say, yeah, yeah, content equals FS read and we provide CLI args dot path and we write await.
Let's just see if that compiles.
So, it doesn't because value used here after move and the reason is if we yeah, have this debug flag then it takes on So, this one takes ownership of the CLI args struct and then I tried to print it. So, I can just take a reference here. So, let's see if that helps it.
And yeah, indeed it compiles.
And yeah, let's just debug the content for now.
Let's see if that works.
And yeah, I get a proper error that it's not found which is perfect. And now let's just take any any file which actually exists.
And, yeah.
I get something.
So, this is great. I get okay and some content.
Awesome.
And, yeah, and then what we need to do is we need to transform the content into something which we can actually handle. And then, we can start parsing the file. So, now we also want the content in a readable form. So, we write read to string instead of read and we get a string back.
So, let's try it again.
And this time, you can actually read the content of cargo.toml. I mean, this will not be the resulting file which we want to read in, but uh, just for you to understand technically how it works. Now, uh, we can also put a question mark here to unwrap it and get the actual content. And now, we have the actual content as a string.
And with that, I conclude the first video.
The next video, we will then parse the content, each markdown sections, and then do something with them.
I hope you enjoyed this video. It is very basic because I have to get back into Rust. I have to learn quite a few concepts for this whole thing to work.
So, bear with me. I hope you enjoyed it nevertheless and learned something.
Thanks for watching and stay tuned.
Related Videos

TOP 15 Data compression Interview Questions and Answers 2019 Part-2 | Data compression | Wisdom jobs
wisdomjobs
281 views•2019-06-28

CTS 158: 802.11w Management Frame Protection
ClearToSend
4K views•2019-02-04

NDSS 2019 Send Hardest Problems My Way: Probabilistic Path Prioritization for Hybrid Fuzzing
NDSSSymposium
496 views•2019-04-02

How realistic is Cities: Skylines?
CityBeautiful
159K views•2019-02-14

GUIs & TUIs: Choosing a User Interface for Your Python Project | Real Python Podcast
realpython
2K views•2025-04-04

The OSI Model - Explained by Example
hnasr
225K views•2019-05-12

Cloud Computing - Introduction
elithecomputerguy
98K views•2019-10-07

From Traveler's Dilemma to Dynamic Routing | Demystifying Networking
IITBombayJuly
5K views•2019-08-04
Trending

Playstation NO DISC/NO BUY Fight Is Over...
DavidJaffeGames
4K views•2026-07-23

Steam and Xbox Just Dropped The Hammer On PlayStation
OhNoItsAlexx
9K views•2026-07-23

Americans Confused in Australia for 17 Minutes Straight
IWrocker
17K views•2026-07-23

SuperBike Factory Has Gone... What's Next for the Motorcycle Industry?
thatbikersimon
11K views•2026-07-22