<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Clap on PHP Boy Scout</title><link>https://phpboyscout.uk/tags/clap/</link><description>Recent content in Clap on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Mon, 13 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://phpboyscout.uk/tags/clap/index.xml" rel="self" type="application/rss+xml"/><item><title>Which CLI library should you start with?</title><link>https://phpboyscout.uk/which-cli-library-should-you-start-with/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><guid>https://phpboyscout.uk/which-cli-library-should-you-start-with/</guid><description>&lt;img src="https://phpboyscout.uk/which-cli-library-should-you-start-with/cover-which-cli-library-should-you-start-with.png" alt="Featured image of post Which CLI library should you start with?" /&gt;&lt;p&gt;You&amp;rsquo;ve decided to build a command-line tool. Good. And before you&amp;rsquo;ve written a single line of the thing it&amp;rsquo;s actually meant to do, you hit a decision that feels far more permanent than it should: which library parses the arguments. You go looking for the answer, and instead of an answer&amp;hellip; you get a scrap. The Go crowd will tell you &lt;code&gt;flag&lt;/code&gt;, or Cobra, or urfave/cli, usually with a bit of heat. The Rust crowd will tell you clap, and then argue about how you ought to hold it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve shipped CLIs in both languages, and wired both into frameworks other people build on (&lt;a class="link" href="https://gitlab.com/phpboyscout/go-tool-base" target="_blank" rel="noopener"
 &gt;go-tool-base&lt;/a&gt; and &lt;a class="link" href="https://gitlab.com/phpboyscout/rust-tool-base" target="_blank" rel="noopener"
 &gt;rust-tool-base&lt;/a&gt;). So here&amp;rsquo;s the map I wish I&amp;rsquo;d had. Not &amp;ldquo;the best one&amp;rdquo;, because there isn&amp;rsquo;t one, but where to &lt;em&gt;start&lt;/em&gt;, and the single thing that tells you when you&amp;rsquo;ve outgrown it.&lt;/p&gt;
&lt;h2 id="go-a-ladder-you-climb"&gt;Go: a ladder you climb
&lt;/h2&gt;&lt;p&gt;Go hands you a perfectly good argument parser in the standard library, &lt;code&gt;flag&lt;/code&gt;, and for a great many tools that is the entire answer. A couple of options, no subcommands, done:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;world&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;who to greet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hello, %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No dependency, no ceremony, and it&amp;rsquo;s already on your machine. If your tool is one verb and a handful of flags, stop reading and use this. Reaching for anything bigger is just wiring you&amp;rsquo;ll maintain for nobody&amp;rsquo;s benefit.&lt;/p&gt;
&lt;p&gt;The moment &lt;code&gt;flag&lt;/code&gt; runs out of road is a specific one, and it pays to know exactly where it sits&amp;hellip; &lt;strong&gt;nested subcommands.&lt;/strong&gt; The instant you want &lt;code&gt;mytool build&lt;/code&gt; and &lt;code&gt;mytool deploy&lt;/code&gt; to be separate commands, each with its own flags and its own &lt;code&gt;--help&lt;/code&gt;, &lt;code&gt;flag&lt;/code&gt; has nothing for you and you&amp;rsquo;re hand-rolling a tangle of switch statements. That&amp;rsquo;s the signal to climb.&lt;/p&gt;
&lt;p&gt;At the top of the ladder is &lt;a class="link" href="https://cobra.dev/" target="_blank" rel="noopener"
 &gt;Cobra&lt;/a&gt;. It&amp;rsquo;s the one behind kubectl, docker, hugo, gh and helm, north of 170,000 projects all told, so if you&amp;rsquo;ve ever used a Go CLI you&amp;rsquo;ve used it. Subcommands, generated help, shell completions, man pages: all the furniture a &amp;ldquo;grown-up&amp;rdquo; tool eventually wants. The cost is ceremony. A &lt;code&gt;cobra.Command&lt;/code&gt; per verb, usually &lt;a class="link" href="https://github.com/spf13/viper" target="_blank" rel="noopener"
 &gt;Viper&lt;/a&gt; alongside it for config, and a fair pile of boilerplate before the tool does anything at all.&lt;/p&gt;
&lt;p&gt;Between the two sits &lt;a class="link" href="https://cli.urfave.org/" target="_blank" rel="noopener"
 &gt;urfave/cli&lt;/a&gt;, the option people forget to mention in the fight. It does subcommands like Cobra, but with a lighter, more declarative feel, and if Cobra&amp;rsquo;s structure looks like more than you need, it&amp;rsquo;s the sensible middle rung.&lt;/p&gt;
&lt;p&gt;So Go is a genuine ladder: start on &lt;code&gt;flag&lt;/code&gt;, and climb to a framework the day subcommands arrive. Or don&amp;rsquo;t wire Cobra and Viper together yourself at all. That assembly is precisely the job &lt;a class="link" href="https://gitlab.com/phpboyscout/go-tool-base/-/blob/f93f6b4/go.mod#L39-L41" target="_blank" rel="noopener"
 &gt;go-tool-base wraps up for you&lt;/a&gt;, which is &lt;a class="link" href="https://phpboyscout.uk/building-a-cli-with-go-tool-base-part-1/" &gt;a whole series of its own&lt;/a&gt;. A funnel, I&amp;rsquo;ll grant you, but a fair one.&lt;/p&gt;
&lt;h2 id="rust-a-ladder-you-descend"&gt;Rust: a ladder you descend
&lt;/h2&gt;&lt;p&gt;Rust is a different shape entirely, and that difference is the interesting part of this whole question.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no argument parser in Rust&amp;rsquo;s standard library, and there&amp;rsquo;s no real fight over which crate to use either, because one of them won and won decisively. &lt;a class="link" href="https://docs.rs/clap/latest/clap/" target="_blank" rel="noopener"
 &gt;clap&lt;/a&gt; has getting on for a &lt;em&gt;billion&lt;/em&gt; downloads and is a dependency of some sixty-odd thousand other crates. It&amp;rsquo;s the default in the truest sense of the word: it&amp;rsquo;s what people reach for without stopping to think, and it scales from a single flag to a deep tree of subcommands without you ever swapping libraries. One struct, subcommands and all:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[derive(Parser)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;#[command(subcommand)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;: &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[derive(Subcommand)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;: &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;#[arg(long)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;: &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the &lt;code&gt;derive&lt;/code&gt; style, macros hung on your structs, and it&amp;rsquo;s the ergonomic default (there&amp;rsquo;s a runtime &lt;code&gt;builder&lt;/code&gt; API too, for when you&amp;rsquo;re assembling commands dynamically). It&amp;rsquo;s what we use in &lt;a class="link" href="https://gitlab.com/phpboyscout/rust-tool-base/-/blob/5ba936b/Cargo.toml#L64" target="_blank" rel="noopener"
 &gt;rust-tool-base&lt;/a&gt;, and the reasoning was nothing exotic. We already knew CLI conventions well, we knew rtb needed the configuration depth only a heavy-hitter gives you, and clap&amp;rsquo;s sheer popularity made it the boring, sensible bet. When a crate is that dominant, choosing it is the low-risk move, and rtb goes a step further and &lt;em&gt;generates&lt;/em&gt; the clap wiring so you never write that struct by hand at all.&lt;/p&gt;
&lt;p&gt;Which is why the Rust ladder runs the other way. In Go you start small and climb up toward a framework. In Rust the framework &lt;em&gt;is&lt;/em&gt; the ground floor, and the only real question is whether some specific pressure justifies stepping &lt;em&gt;down&lt;/em&gt; to something smaller:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://crates.io/crates/pico-args" target="_blank" rel="noopener"
 &gt;pico-args&lt;/a&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;a class="link" href="https://crates.io/crates/lexopt" target="_blank" rel="noopener"
 &gt;lexopt&lt;/a&gt;&lt;/strong&gt; when binary size and compile time genuinely matter and you can live without generated help. Zero or near-zero dependencies, and you write the usage text yourself. Grand for a small, sharp tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://crates.io/crates/argh" target="_blank" rel="noopener"
 &gt;argh&lt;/a&gt;&lt;/strong&gt; when you want the derive ergonomics but lighter than clap, and you don&amp;rsquo;t mind that it follows Fuchsia&amp;rsquo;s conventions rather than the Unix ones your users will expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://crates.io/crates/bpaf" target="_blank" rel="noopener"
 &gt;bpaf&lt;/a&gt;&lt;/strong&gt; when your parsing is genuinely unusual and you&amp;rsquo;d sooner compose it from combinators, while keeping the niceties like completions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every one of those is a step down from clap for a reason you can actually name. Without such a reason, clap is the answer, and that isn&amp;rsquo;t laziness. It&amp;rsquo;s the ecosystem having already done the choosing on your behalf.&lt;/p&gt;
&lt;p&gt;(One wrinkle worth a footnote: clap&amp;rsquo;s &amp;ldquo;global&amp;rdquo; flags don&amp;rsquo;t behave the way you&amp;rsquo;d assume once you start nesting commands, which cost me an afternoon and &lt;a class="link" href="https://phpboyscout.uk/claps-global-flag-except-in-a-passthrough-subtree/" &gt;a post of its own&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id="same-rule-opposite-ends"&gt;Same rule, opposite ends
&lt;/h2&gt;&lt;p&gt;Strip the two languages back and the principle underneath is identical: &lt;strong&gt;use the smallest thing that fits your constraints, and let a real need, not a hunch, be the thing that moves you off it.&lt;/strong&gt; What&amp;rsquo;s genuinely interesting is that the two ecosystems put the &lt;em&gt;default&lt;/em&gt; at opposite ends of that rule.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s default sits at the bottom, because the standard library gives you &lt;code&gt;flag&lt;/code&gt; for nothing and moving up to Cobra is a switch you make on purpose. Rust&amp;rsquo;s default sits at the top, because the standard library gives you nothing at all and one crate grew so dominant that starting anywhere else is the choice you&amp;rsquo;d have to justify. Go makes you pick and grow into it. Rust makes you descend under pressure.&lt;/p&gt;
&lt;p&gt;So the real answer to &amp;ldquo;which should I start with&amp;rdquo; isn&amp;rsquo;t a library. It&amp;rsquo;s a direction. In Go, start at the bottom and climb the day the subcommands turn up. In Rust, start at the top and only ever climb &lt;em&gt;down&lt;/em&gt;, and only when something forces your hand. Either way, you&amp;rsquo;ll know the exact moment you need to move, which is the only thing the question was ever really asking.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cover: the &lt;a class="link" href="https://go.dev/blog/gopher" target="_blank" rel="noopener"
 &gt;Go gopher&lt;/a&gt; by Renée French (&lt;a class="link" href="https://creativecommons.org/licenses/by/3.0/" target="_blank" rel="noopener"
 &gt;CC BY 3.0&lt;/a&gt;) and &lt;a class="link" href="https://rustacean.net/" target="_blank" rel="noopener"
 &gt;Ferris&lt;/a&gt; (CC0), reimagined in cut paper.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>