Environment and Process Management

Command-Line Arguments

Command-line arguments are the sequence of string tokens passed by the operating system or shell to a program when it starts. In Rust, std::env::args() returns an iterator over these arguments as owned String values, starting at index 0 with the program invocation path followed by user-supplied flags and positional arguments.

Shell invocation:   ./backup --compress -o /tmp/out.tar src/ docs/

Index:       0              1          2        3          4       5
       +------------+--------------+------+--------------+------+-------+
argv:  | "./backup" | "--compress" | "-o" | "/tmp/out.tar| "src/"|"docs/"|
       +------------+--------------+------+--------------+------+-------+
         Program      Boolean Flag   Opt    Option Value   Positional Args

Inspecting Command-Line Arguments

Collecting env::args() into a Vec<String> allows random access by index and slice pattern matching.

use std::env;

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

    println!("Total arguments (including program name): {}", args.len());
    for (index, arg) in args.iter().enumerate() {
        println!("  arg[{index}] = {arg:?}");
    }
}
Total arguments (including program name): 1
  arg[0] = "target/debug/app"

Note that arg[0] is controlled by the caller invoking execve and is not guaranteed to be a valid path—or even present if an empty argument vector was passed via Command. Always use .get(0) rather than unchecked indexing if args could be empty.

State-Machine Argument Parsing with an Iterator

For lightweight command-line tools that do not require an external crate, consuming an iterator directly with while let Some(arg) = iter.next() cleanly handles flags (--verbose), key-value options (--port 8080 or --port=8080), and positional arguments.

#[derive(Debug, Default)]
struct Config {
    verbose: bool,
    port: u16,
    inputs: Vec<String>,
}

fn parse_args<I>(mut args: I) -> Result<Config, String>
where
    I: Iterator<Item = String>,
{
    let mut config = Config {
        verbose: false,
        port: 8080,
        inputs: Vec::new(),
    };

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-v" | "--verbose" => config.verbose = true,
            "-p" | "--port" => {
                let raw = args
                    .next()
                    .ok_or_else(|| "missing value for --port".to_string())?;
                config.port = raw
                    .parse::<u16>()
                    .map_err(|_| format!("invalid port number: {raw}"))?;
            }
            s if s.starts_with("--port=") => {
                let raw = &s["--port=".len()..];
                config.port = raw
                    .parse::<u16>()
                    .map_err(|_| format!("invalid port number: {raw}"))?;
            }
            "--" => {
                // Everything after "--" is treated as a positional argument
                config.inputs.extend(args);
                break;
            }
            s if s.starts_with('-') => {
                return Err(format!("unknown flag: {s}"));
            }
            positional => config.inputs.push(positional.to_string()),
        }
    }

    Ok(config)
}

fn main() {
    let simulated = vec![
        "--verbose".to_string(),
        "--port=9090".to_string(),
        "input.txt".to_string(),
        "--".to_string(),
        "-literal-dash-file.txt".to_string(),
    ];

    let config = parse_args(simulated.into_iter()).unwrap();
    println!("{config:?}");
}
Config { verbose: true, port: 9090, inputs: ["input.txt", "-literal-dash-file.txt"] }

Subcommand Dispatch with Slice Patterns

Converting Vec<String> references to a slice of &str enables ergonomic subcommand routing with match:

fn dispatch(args: &[String]) -> Result<String, &'static str> {
    let str_args: Vec<&str> = args.iter().map(String::as_str).collect();

    match str_args.as_slice() {
        ["init", name] => Ok(format!("Initializing repository '{name}'")),
        ["remote", "add", alias, url] => {
            Ok(format!("Added remote '{alias}' -> {url}"))
        }
        ["status"] => Ok("Working tree clean".to_string()),
        _ => Err("Usage: tool <init <name> | remote add <alias> <url> | status>"),
    }
}

fn main() {
    let cmd = vec![
        "remote".to_string(),
        "add".to_string(),
        "origin".to_string(),
        "https://example.com/repo.git".to_string(),
    ];
    println!("{}", dispatch(&cmd).unwrap());
}
Added remote 'origin' -> https://example.com/repo.git