$ cargo new minigrep Created binary (application) `minigrep` project $ cd minigrep
The first task is to make minigrep
accept its two command line arguments: the filename and a string to search for. That is, we want to be able to run our program with cargo run
, a string to search for, and a path to a file to search in, like so:
$ cargo run searchstring example-filename.txt
Right now, the program generated by cargo new
cannot process arguments we give it. Some existing libraries on can help with writing a program that accepts command line arguments, but because you’re just learning this concept, let’s implement this capability ourselves.
To enable minigrep
to read the values of command line arguments we pass to it, we’ll need a function provided in Rust’s standard library, which is std::env::args
. This function returns an iterator of the command line arguments that were given to minigrep
. We’ll cover iterators fully in Chapter 13. For now, you only need to know two details about iterators: iterators produce a series of values, and we can call the collect
method on an iterator to turn it into a collection, such as a vector, containing all the elements the iterator produces.
Use the code in Listing 12-1 to allow your minigrep
program to read any command line arguments passed to it and then collect the values into a vector.
Filename: src/main.rs
First, we bring the std::env
module into scope with a use
statement so we can use its function. Notice that the std::env::args
function is nested in two levels of modules. As we discussed in , in cases where the desired function is nested in more than one module, it’s conventional to bring the parent module into scope rather than the function. By doing so, we can easily use other functions from std::env
. It’s also less ambiguous than adding use std::env::args
and then calling the function with just args
, because args
might easily be mistaken for a function that’s defined in the current module.
On the first line of main
, we call env::args
, and we immediately use to turn the iterator into a vector containing all the values produced by the iterator. We can use the collect
function to create many kinds of collections, so we explicitly annotate the type of args
to specify that we want a vector of strings. Although we very rarely need to annotate types in Rust, collect
is one function you do often need to annotate because Rust isn’t able to infer the kind of collection you want.
Finally, we print the vector using the debug formatter, :?
. Let’s try running the code first with no arguments and then with two arguments:
$ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.61s Running `target/debug/minigrep` ["target/debug/minigrep"]
$ cargo run needle haystack Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 1.57s Running `target/debug/minigrep needle haystack` ["target/debug/minigrep", "needle", "haystack"]
Notice that the first value in the vector is "target/debug/minigrep"
, which is the name of our binary. This matches the behavior of the arguments list in C, letting programs use the name by which they were invoked in their execution. It’s often convenient to have access to the program name in case you want to print it in messages or change behavior of the program based on what command line alias was used to invoke the program. But for the purposes of this chapter, we’ll ignore it and save only the two arguments we need.
Filename: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); let query = &args[1]; let filename = &args[2]; println!("Searching for {}", query); println!("In file {}", filename); }
Listing 12-2: Creating variables to hold the query argument and filename argument
As we saw when we printed the vector, the program’s name takes up the first value in the vector at args[0]
, so we’re starting at index 1
. The first argument minigrep
takes is the string we’re searching for, so we put a reference to the first argument in the variable query
. The second argument will be the filename, so we put a reference to the second argument in the variable filename
.
We temporarily print the values of these variables to prove that the code is working as we intend. Let’s run this program again with the arguments test
and sample.txt
:
Great, the program is working! The values of the arguments we need are being saved into the right variables. Later we’ll add some error handling to deal with certain potential erroneous situations, such as when the user provides no arguments; for now, we’ll ignore that situation and work on adding file-reading capabilities instead.