Rust Command-Line Arguments
Most of the small projects worth building as practice — a to-do list, a word counter — need to take input from the command line rather than hardcoding values in the source. This page covers the basics.
Reading arguments with std::env::args
Section titled “Reading arguments with std::env::args”use std::env;
fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args);}If you compiled this into a program called greet and ran:
./greet AliceYou’d see:
["greet", "Alice"]The first element is always the program’s own name — the arguments you actually typed start at index 1.
Reading a specific argument
Section titled “Reading a specific argument”use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Usage: greet <name>"); return; }
let name = &args[1]; println!("Hello, {name}!");}Run with cargo run -- Alice and you’d get:
Hello, Alice!Run it with no arguments, and you’d see the usage message instead of a crash — checking args.len() before indexing avoids a panic if someone forgets to pass anything.
Parsing an argument into a number
Section titled “Parsing an argument into a number”Since every argument comes in as a String, you’ll often need .parse() (from the Type Casting page) to turn one into a number:
use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("Usage: square <number>"); return; }
match args[1].parse::<i32>() { Ok(n) => println!("{}", n * n), Err(_) => println!("That's not a valid number"), }}Handling a simple flag by hand
Section titled “Handling a simple flag by hand”A flag is an argument that switches something on, like --shout. Before reaching for a crate, it helps to see how you’d handle one yourself. The trick that makes this easy to test is to put the logic in a function that takes the arguments as a slice, &[String]. Then main can pass the real arguments, and you can pass a hand-made list while experimenting:
fn run(args: &[String]) { let mut shout = false; let mut names = Vec::new();
// skip index 0, the program name for arg in &args[1..] { if arg == "--shout" { shout = true; } else { names.push(arg); } }
if names.is_empty() { println!("Usage: greet [--shout] <name>..."); return; }
for name in names { let line = format!("Hello, {name}!"); if shout { println!("{}", line.to_uppercase()); } else { println!("{line}"); } }}
fn main() { // In a real program you would write: // let args: Vec<String> = std::env::args().collect(); // Here we build the list by hand, so it also works in the Playground. let args = vec![ String::from("greet"), String::from("--shout"), String::from("Ana"), String::from("Ben"), ]; run(&args);}Output:
HELLO, ANA!HELLO, BEN!A few things to notice:
&args[1..]is every argument except the program name. The flag can appear anywhere:greet Ana --shout Benworks too.- Remove
--shoutfrom the list and run it again: you get the normal greetings. Remove the names too, and you get the usage message. - Because
rundoesn’t care where its list came from, swapping instd::env::args().collect()is a one-line change inmain.
This is fine for one or two flags. Once you also want short versions (-s), values after a flag (--count 3) and a --help message, the hand-written loop grows quickly. That’s the moment to switch to clap.
For anything more than a couple of arguments: use clap
Section titled “For anything more than a couple of arguments: use clap”Manually indexing into args gets unwieldy fast once you need flags (--verbose), optional values, or helpful --help output. The standard solution is the clap crate, added with:
cargo add clap --features deriveuse clap::Parser;
#[derive(Parser)]struct Args { name: String,
#[arg(short, long, default_value_t = 1)] count: u32,}
fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello, {}!", args.name); }}This gives you argument parsing, validation, and a working --help message, all generated from the struct definition. You don’t need clap for quick scripts, but for anything you’ll actually use more than once, it saves a lot of manual work.
Practice
Section titled “Practice”The Playground can’t pass real arguments, so both exercises start from a hand-made Vec<String> that looks just like what env::args().collect() would give you. On your own machine, replace it with the real thing and try cargo run -- ....
1. List the arguments
Section titled “1. List the arguments”Given vec!["notes", "add", "buy milk"] (as Strings), print how many arguments the user typed (not counting the program name), then print each one with its position, starting at 1.
Hint: the count is args.len() - 1. A for i in 1..args.len() loop gives you each index after the program name.
Show solution
fn main() { // pretend this came from std::env::args().collect() let args = vec![ String::from("notes"), String::from("add"), String::from("buy milk"), ];
println!("You passed {} arguments", args.len() - 1); for i in 1..args.len() { println!("{i}: {}", args[i]); }}Output:
You passed 2 arguments1: add2: buy milk"buy milk" counts as one argument: on a real command line you’d type it in quotes (cargo run -- add "buy milk"), and the shell passes it as a single string. Index 0 is still the program name, which is why the loop starts at 1.
2. Add up the numbers
Section titled “2. Add up the numbers”Given vec!["sum", "4", "10", "x", "6"], add up every argument that is a valid number, and print a warning for any that isn’t. Finish by printing the total.
Hint: loop over &args[1..] and use match arg.parse::<i32>(), like in Parsing an argument into a number. Keep a let mut total = 0; outside the loop.
Show solution
fn main() { let args = vec![ String::from("sum"), String::from("4"), String::from("10"), String::from("x"), String::from("6"), ];
let mut total = 0; for arg in &args[1..] { match arg.parse::<i32>() { Ok(n) => total += n, Err(_) => println!("Skipping '{arg}': not a number"), } } println!("Total: {total}");}Output:
Skipping 'x': not a numberTotal: 20Every argument arrives as text, even "4", so each one has to be parsed. Skipping bad input with a message is friendlier than crashing. You could also stop at the first bad one with return, if that suits your program better.
std::env::args().collect()gives you aVec<String>of the command-line arguments; index 0 is always the program name.- Check
args.len()before indexing, so a missing argument doesn’t panic your program. .parse()turns aStringargument into a number, same as elsewhere.- Put your argument handling in a function that takes
&[String], so you can try it with a hand-made list. - For anything beyond a couple of plain arguments, the
clapcrate is the standard, well-worn choice.
Quick check
Section titled “Quick check”Quick check
1. When you call env::args().collect(), what is the first element of the resulting Vec?
2. Why check args.len() before indexing into args?
3. What does the clap crate give you that manual args indexing doesn't?
Score: 0 / 3
Read more
Section titled “Read more”- Accepting Command Line Arguments — The Rust Book: the start of the Book’s
minigrepproject, which reads a search word and a file name from the command line. std::env::args— standard library docs: exactly whatargs()returns, and when it panics (arguments that aren’t valid Unicode).- Program arguments — Rust by Example: a short example, plus a follow-up on matching arguments with
match. - Command Line Applications in Rust: a free mini-book on building real CLI tools, from parsing arguments to testing and shipping them.
- clap derive tutorial — docs.rs: step-by-step guide to flags, options, subcommands and validation with
clap.