12.1.0 Before We Begin
In Chapter 12, we will build a real project: a command-line program. This program is a grep (Global Regular Expression Print), a tool for global regular-expression search and output. Its job is to search for the specified text in the specified file.
This project has several steps:
- Receive command-line arguments (this article)
- Read files
- Refactor: improve modules and error handling
- Use TDD (test-driven development) to develop library functionality
- Use environment variables
- Write error messages to standard error instead of standard output
If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
12.1.1 Standardizing the Input Format
First we need to define a standard input format for passing arguments. I use the following convention:
cargo run text filename.txt
12.1.2 Reading Command-Line Arguments
After standardizing the input, the next problem is reading command-line arguments.
We need a function from Rust’s standard library: std::env::args(). This function returns an iterator (covered in Chapter 13) that yields a series of values. For an iterator, you can use the collect method to turn that series of values into a collection, such as a Vector.
As discussed in 7.4. Use Pt. 1, when a function is nested inside more than one module, we usually bring its parent module into scope.
Here is the code:
use std::env;
fn main() {
let args:Vec<String> = env::args().collect();
}
Because collect produces a collection, but Rust cannot infer the element type of that collection, we must explicitly declare args as Vec<String>.
Let’s use println! to see what happens:
use std::env;
fn main() {
let args:Vec<String> = env::args().collect();
println!("{:#?}", args);
}
Output 1, with no arguments:
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
]
Output 2, with arguments:
$ cargo run -- needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
"needle",
"haystack",
]
The -- in the second example is used to separate the arguments for the Cargo command from the arguments passed to the program. It tells Cargo that what follows is not a Cargo option or argument, but arguments that should be passed to the program when it runs. env::args does not read or store it.
You can see that even without arguments, this Vector still has one element: the currently executing binary itself, which in this example is "target/debug/minigrep". So the arguments we actually need begin at the second element of args, that is, at index 1.
Once we know where the needed arguments are stored, we can declare variables to hold them. Declare query to store the text to search for, and filename to store the file name:
let query = &args[1];
let filename = &args[2];
This works, but if the user enters too few arguments and the index is out of bounds, Rust will panic and stop the program. Of course, you can also combine match with get:
let query = match args.get(1) {
Some(arg) => arg,
None => panic!("No query provided"),
};
let filename = match args.get(2) {
Some(arg) => arg,
None => panic!("No file name provided"),
};
We will use the first approach here.
Then print the two variables so the user can confirm the input:
println!("search for {}", query);
println!("In file {}", filename);
12.1.3 The Full Code
Here is all the code written up to this article:
use std::env;
fn main() {
let args:Vec<String> = env::args().collect();
let query = &args[1];
let filename = &args[2];
println!("search for {}", query);
println!("In file {}", filename);
}
Top comments (0)