Command Line Subcommands in Rust

Here’s the translation of the Go code to Rust, with explanations in Markdown format suitable for Hugo:

use std::env;
use std::process;

fn main() {
    // We declare subcommands using separate functions, and proceed to define
    // new arguments specific for each subcommand.
    let foo_matches = clap::Command::new("foo")
        .arg(clap::Arg::new("enable")
            .long("enable")
            .help("enable")
            .action(clap::ArgAction::SetTrue))
        .arg(clap::Arg::new("name")
            .long("name")
            .help("name")
            .default_value(""))
        .get_matches();

    // For a different subcommand we can define different supported arguments.
    let bar_matches = clap::Command::new("bar")
        .arg(clap::Arg::new("level")
            .long("level")
            .help("level")
            .default_value("0"))
        .get_matches();

    // The subcommand is expected as the first argument to the program.
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("expected 'foo' or 'bar' subcommands");
        process::exit(1);
    }

    // Check which subcommand is invoked.
    match args[1].as_str() {
        // For every subcommand, we parse its own arguments and
        // have access to trailing positional arguments.
        "foo" => {
            println!("subcommand 'foo'");
            println!("  enable: {}", foo_matches.get_flag("enable"));
            println!("  name: {}", foo_matches.get_one::<String>("name").unwrap());
            println!("  tail: {:?}", foo_matches.get_many::<String>("").unwrap().collect::<Vec<_>>());
        }
        "bar" => {
            println!("subcommand 'bar'");
            println!("  level: {}", bar_matches.get_one::<String>("level").unwrap());
            println!("  tail: {:?}", bar_matches.get_many::<String>("").unwrap().collect::<Vec<_>>());
        }
        _ => {
            println!("expected 'foo' or 'bar' subcommands");
            process::exit(1);
        }
    }
}

To run the program, compile it using rustc:

$ rustc command_line_subcommands.rs

First, invoke the foo subcommand:

$ ./command_line_subcommands foo --enable --name=joe a1 a2
subcommand 'foo'
  enable: true
  name: joe
  tail: ["a1", "a2"]

Now try bar:

$ ./command_line_subcommands bar --level 8 a1
subcommand 'bar'
  level: 8
  tail: ["a1"]

But bar won’t accept foo’s flags:

$ ./command_line_subcommands bar --enable a1
error: unexpected argument '--enable' found

Usage: bar [OPTIONS]

For more information, try '--help'.

In this Rust version, we’re using the clap crate for parsing command-line arguments, which is a popular choice in the Rust ecosystem. The clap crate provides a more declarative way to define command-line interfaces compared to the manual parsing done in the original Go example.

The structure of the program remains similar: we define subcommands with their respective arguments, parse the command-line input, and then handle each subcommand separately. The main differences are in the syntax and the use of Rust-specific constructs like match for control flow and the Result type for error handling.

Next, we’ll look at environment variables, another common way to parameterize programs.