Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

12.1 Receiving Command-Line Arguments

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:

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 13.5. Iterators Pt. 1) 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 dbg! to see what happens:

use std::env;

fn main() {
	let args:Vec<String> = env::args().collect();
	dbg!(args);
}

Output 1, with no arguments:

$ cargo run
   Compiling minigrep v0.1.0 (/tmp/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/minigrep`
[src/main.rs:5:2] args = [
    "target/debug/minigrep",
]

Output 2, with arguments:

$ cargo run -- needle haystack
   Compiling minigrep v0.1.0 (/tmp/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/minigrep needle haystack`
[src/main.rs:5:2] 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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
println!("Searching 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!("Searching for {}", query);  
    println!("In file {}", filename);  
}