In this lesson we're going to learn about more about Iterators, specifically using env::args()
.
let mut args = env::args();
The Code
We're going to end up with this code and on the way we'll explore what it means for a type in Rust to "be an Iterator".
use std::{env, fs::File, io::Write};
fn main() {
let mut args = env::args();
// skip the program name argument
args.next();
let layout = args
.next()
.expect("The first argument must be the layout but there was no value.");
let tags = args
.next()
.expect("The second argument must be the tags but there was no value.");
let heading = args
.next()
.expect("The third argument must be the heading but there was no value.");
let filename = args
.next()
.expect("The fourth argument must be the filename but there was no value.");
let new_file_contents = format!(
"---
layout: {layout}
tags: {tags}
status: draft
---
# {heading}
"
);
let mut file = File::create(filename).unwrap();
file.write_all(new_file_contents.as_bytes()).unwrap();
}
Content
Currently, we're accessing the arguments passed to our CLI in the Vec we collected by index. This is ok, but if we don't pass in the right number of arguments the program will panic.
For example, if we use cargo run and leave out the last argument.
❯ cargo run post rust,bevy "Doing shader stuff in Bevy"
Then we get a panic because the index we're looking for doesn't exist.
thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 4', src/main.rs:9:21
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is ok. Our program can't continue to run so panic
ing is an ok response, but we don't get to customize the error message in any way which means the users of this program get “index out of bounds” instead of “you must provide the filename” as an error message.
We can fix this by taking advantage of the fact that env::*args*()
is an Iterator.
When we say a struct is an Iterator
, what we mean is that the struct implements the Iterator trait.
env::args()
returns the struct Args
., which implements Iterator
.
This means we get access to the next()
function, which is required for all iterators.
We can remove the collect()
call at the end of our Iterator, which will store Args
in the args
variable. We need mutable access to the Iterator to call .next(), which is explicitly called out in the documentation. If we didn't write mut
here, the compiler would also catch it for us.
let mut args = env::args();
With args
set up, we can replace all of the index access.
Each time we call .next()
on the Iterator, we get either the next value or None
. This is because .next()
returns an Option
type, which has two possible values: None
or Some
.
If the value is Some
, then our String is contained inside of it and we can unwrap() the Option to store that String in layout or any of the other variables.
If the value is None
, then .unwrap()
will panic, crashing our program like before.
Remember to also include one .next()
right after args
to remove the name of the binary, which is always at the beginning of the CLI args list.
let mut args = env::args();
// skip the program name argument
args.next();
let layout = args.next().unwrap();
let tags = args.next().unwrap();
let heading = args.next().unwrap();
let filename = args.next().unwrap();
If we cargo run with fewer arguments than we need now, we still panic with a different error.
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:10:32
Which isn't much better! but we can now improve the error messages.
.expect()
does exactly the same thing as .unwrap()
but it allows us to specify a message. We can replace unwrap()
in each call, providing a different error message for each expected argument.
let layout = args
.next()
.expect("The first argument must be the layout but there was no value.");
let tags = args
.next()
.expect("The second argument must be the tags but there was no value.");
let heading = args
.next()
.expect("The third argument must be the heading but there was no value.");
let filename = args
.next()
.expect("The fourth argument must be the filename but there was no value.");
We still panic the program if we aren't given the right arguments, but now the user will see an error message indicating what actually went wrong instead of an index access error.
thread 'main' panicked at 'The fourth argument must be the filename but there was no value.', src/main.rs:11:32