DEV Community

Cover image for [Rust Guide] 12.1. Receiving Command-Line Arguments
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 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:

  • 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
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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"),  
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);  
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)