Friday 24 November 2017
I've built a lot of command-line tools over the years, mostly in C or C++, but also in Java, Python, Rust, Turbo Pascal, etc. I mostly rolled my own command-line parsing because I did not find getopt very useful, and in most of those languages importing third-party libraries is more effort than reward for such a relatively simple task. In Rust I used the clap library because it provides more functionality (e.g. useful help text) at a lower cost (Rust makes managing library dependencies so easy). I recently discovered structopt and tried it out. Finally, the one true way to parse command-line options!
With structopt you provide a Rust data structure defining a simple AST for your command-line options. You mark it up with structopt-specific attributes describing more details of the syntax, e.g. option names. Markup is minimized using sensible defaults and leveraging Rust conventions: doc-comments are used as help text, Option makes arguments optional, subcommands use enums, value are parsed using the FromStr trait, etc. Then structopt generates code (using Rust's "custom derive") that parses the command line options into your data structure. It uses clap underneath so you get helpful error messages, support for generating shell completion scripts, etc. But unlike using clap directly, you get the final parsed results which you can use from Rust code in a completely natural way, e.g. using match on subcommand enums. It's beautiful. It also has practical advantages for maintenance: for example, unused arguments are unused fields and produce compiler warnings. Take a look at the examples.
This is a special case of deserialization. A lot of programming is serializing and deserializing, and we have also used Rust's serde library to great effect. There must be more opportunities to use Rust custom-derive to implement DSLs for specialized serialization and deserialization problems.