Argument Parsing in Rust
When you type git --help
on Linux terminal, you will see the prints like:
shell> git --help
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
These are common Git commands used in various situations:
start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one
...... <omitted>
Git program supports a long list of arguments. We take it an example to talk about what the arguments of a program should be like.
To give a good command line interface to users, the argument handling part of a program should support follow features:
- Options that takes parameters
- Flags/Switches that do not take any parameter
- Both long name (starting with double hyphen like
--paginate
) and short name (starting with single hyphen like-p
) - Friendly help message with
--help
- Message for wrong parameters
- Subcommands (like
git add
)
Taking Program Arguments in Rust
The Rust Book introduced how to take the arguments to a Rust program by using std::env
. To understand what the user has inputted, your main()
function may start with:
fn main() {
let args: Vec<String> = std::env::args().collect();
for arg in args {
println!("{arg}");
}
}
Run the program with some random parameters, you will see:
shell > ./target/debug/hello --kind apple --count 42
./target/debug/hello
--kind
apple
--count
42
You can obtain all the input parameters with the form of a list of string. But that is only the beginning. Now you have to handle these strings in following aspects:
- Parsing the argument names and validating them
- Parsing the argument values and convert if they are numbers
- Formatting the help message
- Giving useful prompt message for any missing argument or any argument error
- If you want to support subcommand, all the work mentioned above need to be done for each subcommand
The parsing of the input will be a headache if your program support a rich set of argument. It would be a good idea to introduce an external crate in your Rust program to take care of the user input, so you can concentrate on your own logic of the program.
Argh
argh is my favorite argument parsing crate. Because it’s simple to use, and it has a very small memory footprint.
Now I will create a demo program to handle the arguments I have played in the previous command line example. I assume this is a program used to order fruit. I need to tell the program what kind of fruit I need (with --kind
parameter) and how many to order (with --count
).
Here is all the code I need to write to parse the arguments by leveraging argh
:
use argh::FromArgs;
#[derive(FromArgs)]
/// A demo for argument handling
struct Args {
/// set fruit kind
#[argh(option, long = "kind", short = 'k')]
kind: String,
/// set fruit count
#[argh(option, short = 'c', default = "12")]
count: usize,
/// print version information
#[argh(switch, short = 'v')]
version: bool,
}
fn main() {
let args: Args = argh::from_env();
println!("{}, {:x}", args.kind, args.count);
if args.version {
println!("demo v0.0.1");
}
}
Run the code with --help
argument, you can see that argh
has created a standard user interface for my demo program.
shell > ./target/debug/hello --help
Usage: hello -k <kind> [-c <count>] [-v]
A demo for argument handling
Options:
-k, --kind set fruit kind
-c, --count set fruit count
-v, --version print version information
--help display usage information
With argh
you can handle the arguments well in a very easy way:
- In the annotation
#[argh()]
you can specify the long name and short name of the argument. The annotated variable will take the value. By default, the variable name itself is used as the long name of the argument if thelong
keyword is not specified. - The argument can have a default value in case the user omit it.
- Being applied
#[argh(switch)]
, an argument become a flag that doesn’t take any parameter. - Help message is created from the comments.
- If the parameter is numerical, the conversion is done automatically.
argh
validates the input, and display error message if there is any problem in the command:
# missing argument
shell >./target/debug/hello
Required options not provided:
--kind
Run hello --help for more information.
# wrong type
shell >./target/debug/hello --kind apple --count a
Error parsing option '--count' with value 'a': invalid digit found in string
Run hello --help for more information.
argh
can also handle subcommand. It works in the similar way like this example but is a little bit more complex. I will not show it here. Please read the document if you need.
Alternatives
argh
is not the only option to parse the application arguments.
A Github project Rust Arg Parsing Benchmarks compared some well-known argument parsing crates.
The project also provides a guideline for you to balance between these different crates. On the time of write, the page only compares bpaf
and clap
.
My personal opinion is: clap
is the most powerful one, but it is also the biggest one in binary size. If you care the size, and the arguments of your program is not very complex, argh
can be a good choice.