An old-new take on argument parsing in Rust
Blog System/5
SubscribeSign in
An old-new take on argument parsing in Rust<br>Extending getopts into a small framework for Unix-style Rust command-line tools.
Julio Merino<br>Jul 31, 2026
Share
Over the years, I’ve written tens of command-line applications in many different languages—shell is probably the top contender, believe it or not—and for various ecosystems. Along the way, I’ve developed… let’s say… opinions on how they should behave. But behavior and implementation are different topics, and today I would like to talk a little about the latter in the Rust ecosystem.<br>The ecosystem
A big theme behind those opinions is that consistency usually wins: when designing an application, you should target an ecosystem and make sure the tool feels “at home” within it instead of reinventing the way it accepts arguments or presents help.<br>But what is the ecosystem?<br>Is it the language the tool is written in, or…
is it the set of tools with which it plays?
For example: if you were to write a command-line application in Go, you’d naturally reach for the built-in flag library to define flags. Doing so would make the tool feel normal to other Go developers and would make it easier to “read” to them—but the end user does not care, dare I say… at all, which language your tool is written in. So if they try to use such a tool in the context of standard Unix tools like those provided by coreutils or textutils, your tool will feel out of place. And that is what matters to me: I develop tools for a certain ecosystem, not for a language, and I want those tools to integrate well no matter which language they are written in.<br>I mentioned Go right above because Go is the prime example of opinionated choices that “leak” in various ways. This article is about Rust, however, so let’s switch languages.<br>But before we do, take a moment to subscribe to Blog System/5 to demonstrate your support. It’s free if you want it to be!
Subscribe
I can make your hands clap
When writing Rust command-line applications, the expectation nowadays—or rather, assumption—is that you’ll use the clap crate to parse options and arguments. Funnily enough, this assumption is so ingrained in the ecosystem that, when I asked a late-2025 coding agent to review a codebase of mine, it hallucinated that I was using clap even when such crate was nowhere to be found.<br>Here is what a simple clap-based hello world app looks like:
Sample clap-based tool. On the left, the source code. On the right, an invocation without arguments and one invoking help.<br>I will not deny that the resulting app looks nice and that the declarative idiom to define this interface is concise and very powerful. But the result is… out of place with other programs because of all these colors (I know they can be disabled). Also, the code is a bit too magical, as usually happens with #[derive]-style libraries (I know you can opt out of that).<br>And yet… even with all the bells and whistles, the library doesn’t provide enough mechanisms to define an app “end to end”. You see: Rust’s main can return an ExitCode, which is enough to report success or failure to the caller, but this still leaves the application’s control flow in your hands. Because you have to explicitly call clap within main, there is no guarantee that you do it at “the right time”: you might be tempted to parse config files before parsing arguments and other nasty things like that, which can then lead to weird behavior like --help not working if the config file is malformed or on an unavailable network drive.<br>clap isn’t the only game in town though. There are indeed other Rust libraries to parse command lines, with argh being another popular choice. Some of these are also built around derives, some make different tradeoffs around help text and output style, and some smaller alternatives focus mostly on parsing options. This is all fine, but it still doesn’t give me the small Unix-y framework I wanted: something that treats options and positional arguments as one interface, validates both consistently, and owns the startup sequence from parsing to exit code.<br>Enter simpler times
For all of the reasons above, I’ve developed “my own ways” to parse options and arguments in Rust so that they align with more traditional Unix-y programs. In doing so, I ended up writing my own library. I initially wrote this library in the context of the EndBOX where I had to implement various system services and wanted:<br>to enforce consistency among them with as little code duplication as possible, and
to ensure integration into the host’s ecosystem of Unix-y tools provided by the NetBSD base image.
I called that library libapp at the time and, in the fall of 2025, I thought of cleaning it up a little and publishing it. So, today, I want to belatedly announce getoptsargs. Mind you, I had drafted this article back in November but never published it, so today is the day. Better late than never.<br>You might be...