Command-Line Args

Reviewed & published by Brayan K

Command-line arguments are the values a user types after a program's name, read in Rust through std::env::args() as an iterator of Strings — with the program's own name always sitting at index 0.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll collect arguments into a Vec<String>, skip the program name, parse arguments into numbers with parse, read environment variables with env::var, and build a small argument-driven program.

What You'll Learn in This Lesson

1️⃣ Reading args() and the Program Name

std::env::args() gives an iterator of Strings; .collect() turns it into a Vec<String> you can index and count. Remember that args[0] is always the program's own name, so your real arguments begin at args[1]. A common idiom is .skip(1) to step over the program name.

// std::env::args() yields the command-line arguments as an iterator of Strings.
// The FIRST item (index 0) is the program's own name/path, not a real argument;
// your actual arguments start at index 1.
//
// The Rust Playground can't pass real args, so to get reproducible output we
// SIMULATE them by building a Vec<String> exactly like args().collect() would.
fn main() {
    // In a real program this line would be:
    //   let args: Vec<String> = std::env::args().collect();
    let args: Vec<String> = vec![
        String::from("greeter"),   // args[0] = the program name
        String::from("hello"),     // args[1] = first real argument
        String::from("world"),     // args[2] = second real argument
    ];

    println!("program name (args[0]): {}", args[0]);
    println!("number of args (incl. program): {}", args.len());

    // Print just the real arguments (everything after the program name).
    println!("real arguments:");
    for (i, arg) in args.iter().skip(1).enumerate() {
        println!("  arg {} = {}", i + 1, arg);
    }
}

We simulate the arguments here for reproducibility; in a real program the first line would be let args: Vec<String> = std::env::args().collect(); and everything else stays the same.

2️⃣ Parsing Arguments Into Numbers

Arguments are always text, so to compute with them you parse() the String into a number, annotating the target type. parse returns a Result because the text might not be a valid number — handle it with match for a clear error, or unwrap_or for a default.

// Arguments always arrive as Strings. To do math you must parse() them into a
// number, which returns a Result you handle (here with a match).
fn main() {
    // Simulated args: program name, then two numbers as text.
    let args: Vec<String> = vec![
        String::from("adder"),
        String::from("12"),
        String::from("30"),
    ];

    // Parse args[1] and args[2] from &str into i64.
    let a: i64 = match args[1].parse() {
        Ok(n) => n,
        Err(_) => {
            println!("'{}' is not a number", args[1]);
            return;
        }
    };
    let b: i64 = args[2].parse().unwrap_or(0);

    println!("a = {}", a);
    println!("b = {}", b);
    println!("{} + {} = {}", a, b, a + b);
}

3️⃣ Environment Variables with env::var

Beyond positional arguments, std::env::var(name) reads a named environment variable from the shell, returning a Result: Ok(value) if set, Err if not. Pair it with unwrap_or_else to supply a sensible default for missing variables.

use std::env;

// std::env::var(name) reads an environment variable, returning a Result:
// Ok(value) if set, Err(_) if not. Use unwrap_or to supply a fallback.
fn main() {
    // Set a variable from within the program so the example is reproducible.
    // (Normally it's set in your shell, e.g. `APP_MODE=debug cargo run`.)
    env::set_var("APP_MODE", "debug");

    // Read it back with a fallback for the case where it's missing.
    let mode = env::var("APP_MODE").unwrap_or_else(|_| String::from("release"));
    println!("APP_MODE = {}", mode);

    // A variable we did NOT set falls back to the default.
    let level = env::var("LOG_LEVEL").unwrap_or_else(|_| String::from("info"));
    println!("LOG_LEVEL = {}", level);

    // You can also match on the Result directly.
    match env::var("APP_MODE") {
        Ok(v) => println!("APP_MODE is set to '{}'", v),
        Err(_) => println!("APP_MODE is not set"),
    }
}

We call set_var here only to make the example self-contained; in real use you'd set APP_MODE in your shell before running. The unset LOG_LEVEL falls back to its default "info".

Your turn. Fill in the blanks marked ____, then run it.

// 🎯 YOUR TURN — parse the two argument strings and multiply them.
fn main() {
    // Simulated args: program name + two number strings.
    let args: Vec<String> = vec![
        String::from("multiplier"),
        String::from("6"),
        String::from("7"),
    ];

    // Parse both real args to i64 (default to 1 if parsing fails).
    let a: i64 = args[1].____().unwrap_or(1);   // 👉 the string-to-number method
    let b: i64 = args[2].____().unwrap_or(1);

    println!("{} * {} = {}", a, b, a * b);
}

// ✅ Expected output:
//    6 * 7 = 42

Mini-Challenge: A Tiny CLI Calculator

Build an argument-driven calculator that reads an operator and two numbers, then prints the result. Run it with cargo run and check the output.

// 🎯 MINI-CHALLENGE: a tiny arg-driven calculator.
//
// 1. Simulate a command line as a Vec<String>: [program, op, x, y].
//    Use "calc", "add", "8", "5".
// 2. Take op = &args[1], then parse args[2] and args[3] into f64
//    (use .parse().unwrap_or(0.0)).
// 3. match op.as_str() over "add"/"sub"/"mul"/"div" to compute a result;
//    for anything else, print "unknown op: <op>" and return.
// 4. Print the result as: <x> <op> <y> = <result>.
//
// ✅ Example output:
//    8 add 5 = 13

fn main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — std::env

CallGives
env::args()iterator of arg Strings
.collect::<Vec<String>>()all args in a Vec
args[0]the program name
s.parse::<i64>()String to number (Result)
env::var("NAME")an env variable (Result)

🎉 Lesson Complete!

Practice quiz

What type does std::env::args() yield for each item?

  • i32
  • &str
  • String
  • char

Answer: String. args() is an iterator of String, usually collected into a Vec<String>.

Where do a program's real (user-typed) arguments begin?

  • Index 1
  • Index 0
  • Index 2
  • The last index

Answer: Index 1. Index 0 is the program name, so the real arguments start at index 1.

What does this print? let args = vec![String::from("greeter"), String::from("hello"), String::from("world")]; println!("{}", args.len());

  • 1
  • 2
  • 0
  • 3

Answer: 3. len() counts all three entries, including the program name at index 0.

A common idiom to skip the program name when iterating arguments is...

  • .take(1)
  • .skip(1)
  • .rev()
  • .filter(0)

Answer: .skip(1). args.iter().skip(1) steps over the program name to reach the real arguments.

Why does parse() return a Result?

  • It is always fallible because the text may not be a valid number
  • It never fails
  • Because it sorts the input
  • Because strings are immutable

Answer: It is always fallible because the text may not be a valid number. parse can fail when the string is not a valid number, so it returns a Result you handle.

What does this print? let a: i64 = "12".parse().unwrap(); let b: i64 = "30".parse().unwrap_or(0); println!("{}", a + b);

  • 1230
  • 0
  • 42
  • It does not compile

Answer: 42. Both parse to integers, and 12 + 30 is 42.

What determines which numeric type parse() produces?

  • The first character
  • The type annotation on the binding
  • The length of the string
  • It always returns i32

Answer: The type annotation on the binding. The annotated target type (e.g. let a: i64) drives which number parse produces.

What does std::env::var(name) return?

  • Always a String
  • An iterator
  • A bool
  • A Result: Ok(value) if set, Err if not

Answer: A Result: Ok(value) if set, Err if not. var returns a Result because the environment variable might not be set.

How do you supply a fallback for a missing environment variable?

  • env::var(name).panic()
  • env::var(name).unwrap_or_else(|_| default)
  • env::var(name).skip()
  • env::var(name).len()

Answer: env::var(name).unwrap_or_else(|_| default). unwrap_or_else (or unwrap_or) provides a default when the variable is absent.

What is the difference between args() and env::var()?

  • No difference
  • args() reads files; env::var() reads stdin
  • args() reads positional arguments; env::var() reads a named environment variable
  • args() is for numbers only

Answer: args() reads positional arguments; env::var() reads a named environment variable. Arguments are positional inputs to one run; env vars are named configuration from the shell.

Continue this course

Frequently asked questions

How do I read command-line arguments in Rust?

Call std::env::args(), which returns an iterator over the program's arguments as Strings. The most common pattern is to collect it into a vector with let args: Vec<String> = std::env::args().collect(), which lets you index and count the arguments. You then read args[1], args[2], and so on for the real arguments. Each value is owned, so you can keep it around freely. For programs with many flags and options, third-party crates like clap build on this, but the standard library is all you need for simple tools.

Why is the first argument the program name?

By long-standing operating-system convention, the first entry of a program's argument list (index 0) is the path or name used to invoke the program itself, not something the user typed as an option. So if you run a tool as myapp hello world, args[0] is the program name, args[1] is "hello", and args[2] is "world". This means your real arguments always start at index 1, and args.len() counts the program name too. A common idiom is args.iter().skip(1) to iterate only the real arguments.

How do I turn a string argument into a number?

Every argument arrives as a String, so you convert it with the parse method, annotating the target type: let n: i64 = some_arg.parse().unwrap_or(0), or handle the Result with a match for a clear error message. parse returns Result because the text might not be a valid number, and it can produce many numeric types (i32, u64, f64, and more) depending on the type you ask for. Always decide what should happen on bad input — a default with unwrap_or, or an error message and early return.

What is the difference between args() and env::var()?

env::args() reads the positional arguments the user typed after the program name, like filenames or numbers. env::var(name) reads a named environment variable from the surrounding shell session, such as PATH or a custom APP_MODE, and returns a Result because the variable might not be set. Use arguments for the direct inputs to a single run, and environment variables for configuration that tends to persist across runs or is shared by many programs. Both live in the std::env module.