The standard library provides std::env::args() to get command line arguments, the first value is the name of the program, similar to the way arguments are obtained in other languages:

1
2
3
4
5
let args: Vec<String> = env::args().collect();
let query = &args[1];
let filename = &args[2];
println!("Searching for {}", query);
println!("In file {}", filename);

But in the process of product development, we need more program parameters, and need certain rules and checks, this time we need to use some other libraries to parse these parameters, such as the structopt library.

structopt can easily parse command line arguments into a struct.

Here is an official example, which can be tested with cargo run -- --help: cargo run -- --help.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
use std::path::PathBuf;
use structopt::StructOpt;
/// A basic example
#[derive(StructOpt, Debug)]
#[structopt(name = "basic")]
struct Opt {
    // A flag, true if used in the command line. Note doc comment will
    // be used for the help message of the flag. The name of the
    // argument will be, by default, based on the name of the field.
    /// Activate debug mode
    #[structopt(short, long)]
    debug: bool,
    // The number of occurrences of the `v/verbose` flag
    /// Verbose mode (-v, -vv, -vvv, etc.)
    #[structopt(short, long, parse(from_occurrences))]
    verbose: u8,
    /// Set speed
    #[structopt(short, long, default_value = "42")]
    speed: f64,
    /// Output file
    #[structopt(short, long, parse(from_os_str))]
    output: PathBuf,
    // the long option will be translated by default to kebab case,
    // i.e. `--nb-cars`.
    /// Number of cars
    #[structopt(short = "c", long)]
    nb_cars: Option<i32>,
    /// admin_level to consider
    #[structopt(short, long)]
    level: Vec<String>,
    /// Files to process
    #[structopt(name = "FILE", parse(from_os_str))]
    files: Vec<PathBuf>,
}
fn main() {
    let opt = Opt::from_args();
    println!("{:#?}", opt);
}

We define a struct: to hold the command line arguments: Opt , which is defined using macros to define some properties of the arguments. Then with a single line of code let opt = Opt::from_args(); we can resolve the command line arguments to an instance of Opt.

The official library provides a lot of examples that can be used to understand and learn the functions and usage of structopt.

structopt uses parsing as an argument, but by way of macros, which greatly simplifies the use of clap.

First let’s see what structopt does to the above example by means of macros.

structopt macro

structopt implements the structopt::StructOpt trait for Opt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#[allow(unused_variables)]
impl ::structopt::StructOpt for Opt {
    fn clap<'a, 'b>() -> ::structopt:👏:App<'a, 'b> {
        let app = ::structopt:👏:App::new("basic")
            .about("A basic example")
            .version("0.1.0");
        Self::augment_clap(app)
    }
    fn from_clap(matches: &::structopt:👏:ArgMatches) -> Self {
        Opt {
            debug: matches.is_present("debug"),
            verbose: { |v| v as _ }(matches.occurrences_of("verbose")),
            speed: matches
                .value_of("speed")
                .map(|s| ::std::str::FromStr::from_str(s).unwrap())
                .unwrap(),
            output: matches
                .value_of_os("output")
                .map(::std::convert::From::from)
                .unwrap(),
            nb_cars: matches
                .value_of("nb-cars")
                .map(|s| ::std::str::FromStr::from_str(s).unwrap()),
            level: matches
                .values_of("level")
                .map(|v| {
                    v.map(|s| ::std::str::FromStr::from_str(s).unwrap())
                        .collect()
                })
                .unwrap_or_else(Vec::new),
            files: matches
                .values_of_os("file")
                .map(|v| v.map(::std::convert::From::from).collect())
                .unwrap_or_else(Vec::new),
        }
    }
}

fn clap<'a, 'b>() -> clap::App<'a, 'b> generates a clap::App , the name of this App is what we defined basic , we put no defined about property, so here it takes the comment as about description information, this is the pose used by the clap library, clap application definition Some properties and parameters.

But we use structopt not to create a clap app, but only to parse the parameters and map them into a struct, so the clap app created here is just an object to help handle the parameters.

The augment_clap(app) function is also called in the clap() method, which is defined below and defines the app’s arguments.

fn from_clap(matches: &ArgMatches) -> Self is mapping the parameters in the clap’s App object to Opt’s fields.

For example the speed field.

1
2
3
4
speed: matches
    .value_of("speed")
    .map(|s| ::std::str::FromStr::from_str(s).unwrap())
    .unwrap(),

The way to configure the clap app Args is in the function augment_clap:

The attribute configuration of the arguments is generated according to the definition of each field in Opt.

Thus, when we call let opt = Opt::from_args() in our code, we actually call from_clap(&Self::clap().get_matches()).

As a whole, we can see that structopt actually converts various definitions of macros into clap configurations, and we can learn the complex use of its macros.

Properties

The struct you define will map to clap::App, and the non-subcommand fields of this struct will map to clap::Arg. This is set via the attribute #[structopt(...)] is set, so let’s take a look at its properties.

The attributes of structopt can be divided into two categories:

  • structopt’s own magical method : used by structopt itself, attr = ["whatever"] or attr(args...) format
  • raw attributes : mapped to a method call of clap::Arg/App, #[structopt(raw(...))] format

raw properties/methods

property corresponds to clap::App / clap::Arg one by one.

Format.

#[structopt(method_name = single_arg)] or #[structopt(method_name(arg1, arg2))]

magical properties/methods

For example, name, version, no_version, author, about, short, long, rename_all, parse, skip, flatten, subcommand.

For details, see: Magical methods.

Type

A number of built-in types are defined, along with corresponding clap methods.

  • bool: .takes_value(false).multiple(false)
  • Option<T: FromStr>: .takes_value(true).multiple(false)
  • Option<Option<T: FromStr»: .takes_value(true).multiple(false).min_values(0).max_values(1)
  • Vec<T: FromStr>: .takes_value(true).multiple(true)
  • Option<Vec<T: FromStr>: .takes_values(true).multiple(true).min_values(0)
  • T: FromStr: .takes_value(true).multiple(false).required(! has_default)

Subcommands

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[derive(StructOpt)]
#[structopt(about = "the stupid content tracker")]
enum Git {
    Add {
        #[structopt(short)]
        interactive: bool,
        #[structopt(short)]
        patch: bool,
        #[structopt(parse(from_os_str))]
        files: Vec<PathBuf>
    },
    Fetch {
        #[structopt(long)]
        dry_run: bool,
        #[structopt(long)]
        all: bool,
        repository: Option<String>
    },
    Commit {
        #[structopt(short)]
        message: Option<String>,
        #[structopt(short)]
        all: bool
    }
}

Custom String Parsing

If the type does not implement the FromStr trait, or if you just want to customize the parsing method, you can set a custom parsing method.

For more information, see the structureopt documentation doc.rs/structopt.

When developing cli/terminal applications, if you don’t want this declarative way of getting parameters, then you can just use the clap library, which is powerful and widely used.

There are also extensions to structopt based libraries.

paw converts rust main functions into c-style main functions with incoming arguments, which can also be used in combination with structopt:.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::io::prelude::*;
use std::net::TcpListener;
// With the "paw" feature enabled in structopt
#[derive(structopt::StructOpt)]
struct Args {
    /// Port to listen on.
    #[structopt(short = "p", long = "port", env = "PORT", default_value = "8080")]
    port: u16,
    /// Address to listen on.
    #[structopt(short = "a", long = "address", default_value = "127.0.0.1")]
    address: String,
}
#[paw::main]
fn main(args: Args) -> Result<(), std::io::Error> {
    let listener = TcpListener::bind((&*args.address, args.port))?;
    println!("listening on {}", listener.local_addr()?);
    for stream in listener.incoming() {
        stream?.write(b"hello world!")?;
    }
    Ok(())
}

Refurence https://colobu.com/2019/09/29/rust-lib-per-week-structopt/