In this lesson we’re going to learn how to access the individual arguments that we collected in the last lesson. When we run the program, we’ll be able to print out each argument individually.
❯ cargo run post rust,bevy "Doing shader stuff in Bevy" doing-shader-stuff-in-bevy.md
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/first post rust,bevy 'Doing shader stuff in Bevy' doing-shader-stuff-in-bevy.md`
[src/main.rs:13] layout = "post"
[src/main.rs:13] tags = "rust,bevy"
[src/main.rs:13] heading = "Doing shader stuff in Bevy"
[src/main.rs:13] filename = "doing-shader-stuff-in-bevy.md"
The Code
We’re going to end up with this code and on the way we’ll work our way through Ownership.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let layout = args[1].clone();
let tags = args[2].clone();
let heading = args[3].clone();
let filename = args[4].clone();
dbg!(layout, tags, heading, filename);
}
Arguments
When we run our program we can pass any number of arguments to it.
❯ cargo run 1 2 3 4
Compiling first v0.1.0 (/rust-adventure/first-cli)
Finished dev [unoptimized + debuginfo] target(s) in 0.18s
Running `target/debug/first 1 2 3 4`
[src/main.rs:5] args = [
"target/debug/first",
"1",
"2",
"3",
"4",
]
Each of the arguments we pass in comes in order after the name of the program we’re running.
We can associate the position of each argument with a meaning. To do that we have to decide what kind of file we’re scaffolding.
For the workshop we’ll build out a markdown file scaffolding tool, like we were writing a blog post.
First we need to decide what each position will mean.
The first argument will be the post layout, for example a “post” or an image “gallery”.
The second will be a list of tags, like “rust,bevy”.
The third will be the heading of our blog post.
and the fourth will be the file we write the template out to.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args);
let layout = args[1];
let tags = args[2];
let heading = args[3];
let filename = args[4];
dbg!(layout, tags, heading, filename);
}
We’ll also add a dbg!
macro to print out each variable to the console.
We’ve set up the arguments to be accessed by index, much like would be familiar in another programming language, but if we run the program it won’t compile.
❯ cargo run 1 2 3 4
Compiling first v0.1.0 (/rust-adventure/first-cli)
error[E0382]: borrow of moved value: `args`
--> src/main.rs:7:18
|
4 | let args: Vec<String> = env::args().collect();
| ---- move occurs because `args` has type `Vec<String>`, which does not implement the `Copy` trait
5 | dbg!(args);
| ---------- value moved here
6 |
7 | let layout = args[1];
| ^^^^ value borrowed here after move
This is where we first encounter Ownership and Borrowing, one of the defining features of the Rust language.
Every value in Rust has an owner and there can be only one owner at a time. Rust enforces this rule for us, which means we have one less piece of context to “just remember” when we write our programs.
In our case, the variable args
owns the Vec of Strings that contains our arguments.
When we pass the args
variable to dbg!
, we’re giving up ownership of the value to the dbg!
macro. Rust calls this moving, or a move
.
That gives us enough information to read the error message, or at least half of it.
The value in args
is moved when we call dbg!
. Moving a value means we no longer have access to it after that point, but we try to access it again later when we index into it with args[1]
.
Cloning
We have two solutions to this problem
- we can create more copies of the arguments value to pass around
- we can share the access to the original arguments value
In our case, both of these solutions work and both are about the same amount of code.
First we’ll creating more copies. This is often the method that matches what happens in other programming languages.
The method we use to create more copies is called clone
.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args.clone());
let layout = args[1].clone();
let tags = args[2].clone();
let heading = args[3].clone();
let filename = args[4].clone();
dbg!(layout, tags, heading, filename);
}
First we’ll create a copy of the whole Vec
, including the Strings to pass to dbg!
.
Then we’ll clone each of the individual values in the Vec
as well when we access them by index.
Now we can compile and run our program.
❯ cargo run 1 2 3 4
Compiling first v0.1.0 (/rust-adventure/first-cli)
Finished dev [unoptimized + debuginfo] target(s) in 0.20s
Running `target/debug/first 1 2 3 4`
[src/main.rs:5] args.clone() = [
"target/debug/first",
"1",
"2",
"3",
"4",
]
[src/main.rs:12] layout = "1"
[src/main.rs:12] tags = "2"
[src/main.rs:12] heading = "3"
[src/main.rs:12] filename = "4"
When you’re just starting out, cloning is the easiest way around what you’ll think of as “ownership and borrowing issues” and even after you gain more expertise in Rust, cloning will still be an important tool in your toolbox.