Skip to content

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.

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:

Terminal window
./greet Alice

You’d see:

["greet", "Alice"]

The first element is always the program’s own name — the arguments you actually typed start at index 1.

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.

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"),
}
}

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!
▶ Try it Yourself

A few things to notice:

  • &args[1..] is every argument except the program name. The flag can appear anywhere: greet Ana --shout Ben works too.
  • Remove --shout from the list and run it again: you get the normal greetings. Remove the names too, and you get the usage message.
  • Because run doesn’t care where its list came from, swapping in std::env::args().collect() is a one-line change in main.

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:

Terminal window
cargo add clap --features derive
use 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.

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 -- ....

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 arguments
1: add
2: buy milk
▶ Try it Yourself

"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.

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 number
Total: 20
▶ Try it Yourself

Every 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 a Vec<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 a String argument 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 clap crate is the standard, well-worn choice.

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