On the other hand, this situation is a great example of why keeping things like argument parsing out of the Rust standard library is such a good idea. It's much better to let the community gel around some crates which are able to iterate (and innovate) faster than something with strict backwards-compatibility requirements. Looking at the discussion here there's clearly not one way to solve this - there's no way that something as large and complex as clap has evolved into will ever become "standard".
Major versions have good quality migration guides. If only all libraries were developed with such professionalism.
(Python's docopt is also amazing, fwiw)
It has quirks once you try to do something more complex/advanced, but for most of the simple stuff it's very nice to use.
There's also `typer` from the creator of `fastapi` which relies on type annotations. I have not had the opportunity to use it.
https://docs.pydantic.dev/latest/concepts/pydantic_settings/...
https://gist.github.com/porridgewithraisins/313a26ee3b827f73...
I love the ergonomics of this method, and I was going to improve it to support subcommands, etc, but now I think I will use the library you posted.
There's implementations for other languages, too.
TIL about structopt, thanks.
There are many other command-line parsers to choose from that do all the key things that clap does, with half or less the build cost, and most of them with 30x less binary overhead[0]. argh is under 4kloc. gumdrop is under 2kloc. pico-args is under 700loc. What is the value of that extra 10kloc? A 10% better parser?
I am not saying there is no room for a library like clap—it is, at least, a triumphant clown car of features that can handle practically any edge-case anyone ever thought of—but if I got a nickel every time I spent 15 minutes replacing a trivial use of clap with pico-args and thus reduced the binary size and compile time of some project by at least 80%, I would have at least three nickels.
Just to try to pre-empt arguments like “disk space is cheap”, “compiler time is cheaper than human time”, etc.: there are no golden bullets in engineering, only trade-offs. Why would you default to the biggest, slowest option? This is the “every web site must be able to scale like Facebook” type logic. You don’t even have to do more work to use argh or gumdrop. If clap ends up having some magic feature that no other parser has that you absolutely need, you can switch, but I’ve yet to ever encounter such a thing. Its inertia and popularity carry it forward, but it is perhaps the last choice you should pick for a new project—not the first.
These aren't exactly esoteric features, and you're not going to get them for free. I'm happy to pay in space and compile time for clap to gain those features.
This isn't a case of the commandline app needing to be facebook, but rather putting the exponential gains we've made in storage space to good use providing features which should be table stakes at this point.
Phew, several folks have replied to me about how it’s not worth the time thinking about these impacts at all, thus creating a paradox whereby more time has been spent thinking about writing about whether to think about it than has been spent in not thinking about it and just accepting that I wrote a reply on HN about how I feel there are more suitable command-line parsers than clap for most Rust projects! :-)
I agree that much of high-level engineering is knowing whether something is worth thinking about; in this case, I did the thinking already, and I am sharing what I know so others can benefit from (or ignore) my thinking and not have to do so much of their own. If my own personal anecdote of significantly reducing compile times (and binary sizes) by taking a few minutes to replace clap is insufficient, and if the aggregate of other problems I identified don’t matter to others, that’s alright. If reading my comment doesn’t make someone go “huh, I didn’t know {argh|gumdrop|pico-args} existed, clap does seem a little excessive now that you mention it, I will try one of these instead on my next project and see how it goes”, then I suppose they were not the target audience.
I don’t really want to keep engaging on this—as almost everyone (including me) seems to agree, command-line parser minutiae just aren’t that important—but I guess I will just conclude by saying that I believe that anchoring effects have led many programmers to consider any dependency smaller than, say, Electron to be not a big deal (and many think Electron’s fine too, obviously), whereas my experience has been that the difference between good and bad products usually hinges on many such ‘insignificant’ choices combining in aggregate.
Assuming whichever command-line parser one uses operates above a certain baseline—and I believe all of the argparse libraries in that benchmark do—it seems particularly silly to make wasteful choices here because this is such a small part of an application. Choosing wastefulness because it’s technically possible, then rationalising the waste by claiming it increases velocity/usability/scalability/whatever without actually verifying that claim because it’s ‘not worth thinking about’, seems more problematic to me than any spectre of premature or ‘unnecessary’ optimisation. I hope to find better ways to communicate this in future.
There is a tradeoff between compile time and running time.
This matters for programs that run more often than they get compiled.
690KiB is a far compromise if Clap provided, for example, better performance or better code readability and organization. The benchmarks you provided shows the performance is practically the same which is close to no library usage.
I did do a bit of CLI work (I try to maintain https://github.com/rust-starter/rust-starter) and will always pick up clap. It just happens that even for the simplest CLIs out there, things can get hairy really fast. A good type interface that let me define my args as types and have extras on the fly (ie: Shell completion code) is worth the less than 1MiB overhead.
If I ever encounter a single howto or blog post or Stack Overflow answer that tells me how to use Clap to do something 5 minutes more quickly than with an alternative, it’s paid for itself.
Amdahl’s Law says you can’t optimize a system by tweaking a single component and get more than that component’s total usage back. If Clap takes 1% of a program’s resources, optimizing that down to 0 will still use 99% of the original resources.
It’s just not worth it.
But that's not the point. If every dependency follows same philosophy, costs (compiler time, binary size, dependency supply chain) will add up very quickly.
Not to mention, in big organizations, you have to track each 3rd party and transitive dependency you add to the codebase (for very good reasons).
Hahaha, awesome. Thanks for the pico-args recommendation.
It supports the bare minimum.
I sure would like deriving-style parsing and --help auto-generation.
I think deriving-style unavoidably causes build time and complexity.
But it could be done without the dependency explosion.
There's a list of options here:
https://github.com/rosetta-rs/argparse-rosetta-rs#rust-arg-p...
Among the ones you recommend, argh supports deriving, auto-generates --help and optimizes for code size. And its syntax is very comparable to clap, so migrating is very easy. gumdrop seems very similar in its feature set (specifying help strings a little differently), but I can't find a defining feature for it.
Because it's not very big, nor very slow. Why wouldn't you default to the most full-featured option when its performance and space usage is adequate for the overwhelming majority of cases?
This is the logic of buying a Ford F-150 to drive your kids to school and to commute to the office because you might someday need to maybe haul some wood from the home improvement store once. The compact sedan is the obviously practical choice, but it can’t haul the wood, and you can afford the giant truck, so why not?
I think this is more a criticism of rust-analyzer than clap itself, any macro-heavy library I have similar issues with.
(Yes I know clap can be used without derive, but I'm willing to deal with the pain to parse directly into a struct)
It's still derive macro-based, but there's only one derive (`Aargvark`) rather than `Parser`, `Subcommand`, etc, and it can handle any data structure composition orthogonally (although crazy structures may result in fairly awkward command lines).
Looking at this resolved most of my issues about discoverability.
(One edge case that consistently trips me up, which other argument parsers similarly struggle with: an environment variable fallback has the same “weight” as its option counterpart, so any CLI that makes use of grouping/exclusivity will eventually hit user confusions where the user passes `--exclusive` and gets a failure because of an unrelated environment variable.)
It is by far the nicest way to create CLI tools I have ever seen. Every other shell or commandline parsing library I ever tried, feels extremely clunky in comparison.
https://learn.microsoft.com/en-us/powershell/module/microsof...
It's like the node.js of systems languages. Touching it feels gross.
Clap, on the non-derive side, has approximately two dependencies: anstream/anstyle (for terminal coloring, another thing that sounds deceptively simple at first pass, if you think all the world is a VT100, but really isn't; this is a reasonable dep. for a CLI arg parser) and strsim (string similarity, again, a reasonable dep for a CLI arg parser…). And that's it¹ for clap's direct deps.
(¹I'm omitting clap_lex, as an "internal" dep.)
On the derive side, there's the usual proc-macro2/quote/syn trio, but those come up frequently in derive crates due to what they do, and other than that, there's just `heck`, which is again an obvious dependency in context.
… what is so quizzical to me about the "so many dependencies!" complaint is that when we do get examples like this, they're almost always on crates that bottle up genuinely tricky functionality (like what we see here) — exactly the sort of thing that a.) is hard to get right and b.) isn't relevant to the problem I want to solve. That's like "absolutely this is a dependency" central, to me…
This actually reminds me of my other issue with this kind of "oh we just get it for free" attitude that tends to result in overbuilding things that I also dislike in rust.
No I think people would be better off with a bespoke option parser actually.
On the derive side, there's the usual proc-macro2/quote/syn trio, but
those come up frequently in derive crates due to what they do, and other
than that, there's just `heck`, which is again an obvious dependency in
context.
They're common dependencies, sure, but not necessarily the same versions. So, yeah, it's entirely possible you'll end up building multiple versions of quote/syn.But I also share the same overall sentiment. Every moderately sized rust project I've worked on has quite a lot of transitive deps, and that makes me a little bit nervous.
[1] https://doc.rust-lang.org/book/ch02-00-guessing-game-tutoria...
Functions need to build on top of simpler functions to be able to abstract problems and tackle them one at a time. There's innate complexity around and without trying to tame it into smaller functions/packages it seems you'll end up in a worse spot.
I'm not against abstraction and re-use. What I don't like is that for every given thing I want to do, there are multiple crates that offer the same functionality, and it can be really fatiguing trying to vet them. And it is truly a rarity to find a crate that is past the 1.0 version milestone.
Compare to golang for example. You can get quite far in go without needing to pull in any libraries. But in rust you need a library for even a basic http request.
#include <argp.h>, <stdio.h>, <sstream>, or <curl.h>
it'd feel pretty crazy too. Imagine if `make` went out and pulled latest upstream changes for `pthreads` every time any one of your dependencies used it. C++ imagine it's pulling and building boost, or abseil.
C#? The entire mono/.net toolchain and system/ FFI libraries.
Imagine if we had "dot-h.io" that tracked how many separate C projects used argp. Laughable! Millions!
Every language has gobs of dependencies. So many dependencies it'd make you sick. Thousands upon thousands of lines of code, just to make something that runs on one target and says "Hello world" to the screen. Hell, some languages require you to run a runtime on your operating system that runs on real hardware _just to launch those thousands of lines of code_. And those are written using curl.h, pthreads.h, etc etc (or similar). Bananas!
At least those with package managers allow you to see it, audit it, update it seamlessly.
If it's too big, use "nostd"
I've been building clap CLIs for a while and started to put together a template: https://github.com/mootoday/cli-template.
It also includes a crate I developed to reduce the boilerplate code for nested commands: https://crates.io/crates/clap-nested-commands
No help generation
Only flags, options, free arguments and subcommands are supported
A properer parser would knew that --arg2 is a key and will return an error, since the value is missing.
If such behavior is unacceptable to your application, then you have to use a more high-level arguments parsing library.
Yeah, no thank you. If we're talking about 700 LOC, I'm just going to write it myself rather than take on a dependency that won't even describe itself as a proper enough parser. This argument parser doesn't even handle the tedium of generating a help message for the user, and doesn't really parse the arguments -- what's the purpose of using it to do the argument parsing then?So 700 LOC gets us a mediocre argument parser with no features. What do you get for an additional 9300 LOC? A "properer" parser (dev and user experience+). Help generation (dev experience+). Multiple interfaces (dev experience+). Color terminal output (user experience+). Suggested completions (user experience+).
Is it worth it? I dunno that's a per-project choice. If you absolutely need the smallest footprint and compile times possible, probably you don't want to go with clap. You also probably don't want to go with Rust.