CLI tool
google/argh avatar
google/argh

argh derives your CLI from a struct and its doc comments

Rust derive-based argument parsing optimized for code size

1,960 stars104 forksRustBSD-3-Clause

At a glance

What is it?
argh is a Google-authored Rust argument parser built around a derive macro, where doc comments become help text and an attribute decides whether a field is a switch, an option, a positional or a subcommand. It is opinionated, it targets code size, and it conforms to the Fuchsia commandline tools specification.
Who is it for?
Adopt argh if you write small to medium Rust commandline tools and want help output derived from your existing doc comments, since the derive means the documentation and the parser cannot drift apart. Do not adopt it if you need runtime-defined argument sets or a parsing style the derive does not model, and do not expect a large crate behind it.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 148 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The whole public API is a derive and one function

argh describes itself as an opinionated derive-based argument parser optimized for code size, and the second line of the README adds conformance to the Fuchsia commandline tools specification. The public API consists primarily of the `FromArgs` derive and the `from_env` function, which produces a top-level `FromArgs` value from the current program's arguments.

That is genuinely small. There is no builder, no runtime configuration and no parser object to construct. You annotate a struct and call a function.

The basic example shows the whole pattern:

```rust,no_run use argh::FromArgs;

#[derive(FromArgs)] /// Reach new heights. struct GoUp { /// whether or not to jump #[argh(switch, short = 'j')] jump: bool,

/// how high to go #[argh(option)] height: usize,

/// an optional nickname for the pilot #[argh(option)] pilot_nickname: Option<String>, }

fn main() { let up: GoUp = argh::from_env(); } ```

The design decision worth noticing is that the `///` doc comments are the help text. `/// Reach new heights.` becomes the description line and `/// whether or not to jump` becomes the entry in the options list. You cannot write help text that contradicts the code, because there is only one place to write it.

The generated help output is fixed in shape, with a usage line built from the fields, a description, an options block and a help entry that appears as both `--help` and `help`.

Required, optional and repeating come from the type

The switch, option and positional kinds are chosen with the `#[argh(...)]` attribute, but cardinality comes from the field type. A `bool` marked `switch` is a flag that is set to true when provided and is never required. An option can be required, optional or repeating depending on whether it is a bare type, an `Option`, or a `Vec`.

Defaults come from `#[argh(default = "<your_code_here>")]`, and providing one makes the option optional:

rust
#[argh(option, default = "default_height()")]
height: usize,

#[argh(option, default = "String::from(\"only up\")\")]
direction: String,

The default value is an arbitrary Rust expression, evaluated where it is needed, so `default_height()` calling a function works the same as a literal.

Custom types work through the `FromArgValue` trait, which is implemented automatically for anything implementing `FromStr`. When that is not enough, `from_str_fn` takes a function of `fn(&str) -> Result<T, String>`:

rust
#[argh(option, from_str_fn(always_five))]
five: usize,

Positionals are declared with `#[argh(positional)]` and are parsed in declaration order, so the field order in the struct is the argument order on the command line. Only the last positional may be optional, carry a default, or repeat.

Subcommands need three derives, not one

Subcommands are supported by declaring a separate `FromArgs` type per command, an enum that cases over them, and a top-level struct with a `#[argh(subcommand)]` field:

rust
#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
    #[argh(subcommand)]
    nested: MySubCommandEnum,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
    One(SubCommandOne),
    Two(SubCommandTwo),
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "one")]
struct SubCommandOne {
    #[argh(option)]
    /// how many x
    x: usize,
}

The `name` attribute sets the word the user types, and `short = 't'` gives the enum variant a short alias, which is why `SubCommandTwo` is invocable as `two` or `t`. Note the placement of `#[argh(subcommand, name = "one")]` on the struct itself, before the struct definition: the name belongs to the command, not the field.

The `PartialEq` and `Debug` derives in the examples are not required by argh, they are there because the examples assert on the parsed value in tests. That is a good pattern for CLI work, since asserting on the parsed struct is far more readable than asserting on process output.

Custom descriptions, examples and the fixed help layout

Beyond doc comments, you can override the top-level description and add an examples section through attributes on the struct, with `{command_name}` as the placeholder for the binary name:

rust
#[argh(
    description = "{command_name} is a tool to reach new heights.\n\n\
    Start exploring new heights:\n\n\
    \u{00A0} {command_name} --height 5 jump\n\
    ",
    example = "\
    {command_name} --height 5\n\
    {command_name} --height 5 j\n\
    {command_name} --height 5 --pilot-nickname Wes jump"
)]

That escape hatch is worth knowing about but it is also the point where the library stops being opinionated in your favour: you are writing a format string with embedded newlines and a non-breaking space escape, and the result has to line up with the sections argh generates around it.

The generated layout is: a usage line, the description, an optional Examples block when you supply one, the options list with two-space indentation, and a Commands section for subcommands. Because the layout is fixed, help output diffs cleanly between versions, which is what a spec conformance goal implies.

For debugging, the README explains that the derive can be inspected with `cargo-expand`, installed with `cargo install cargo-expand`, which requires a nightly Rust toolchain. The example to expand is in `argh/examples/simple_example.rs`.

What the workspace tells you, and where to stop

The repository is a four-member Cargo workspace:

toml
members = [
    "argh",
    "argh_derive",
    "argh_shared",
    "argh_complete",
]

`argh_derive` is the proc macro, `argh_shared` holds code shared between the macro output and the runtime, and `argh_complete` is the shell completion package. The split means the macro and the runtime agree on types without a circular dependency.

The workspace package version is 0.1.19, edition 2018, BSD-3-Clause, authored by three Google engineers including Taylor Cramer, Benjamin Brittain and Erick Tryzelaar. The README carries an explicit note that this is not an officially supported Google product, which is the honest framing for a crate hosted under the `google` organisation.

Two things to be clear about. First, `argh_complete` is a separate dependency, so shell completion is opt-in rather than bundled. Second, this is a small library with a small feature set: there is no config file support, no environment variable fallbacks, no argument groups, and no validation beyond what `FromStr` and `FromArgValue` give you. That is fine for a focused tool and wrong for a tool with 60 flags.

argh's stated reasons for existence are code-size optimization and Fuchsia commandline tools specification conformance. The practical question is whether your binary is large enough for that to matter.

On maintenance, the last push was on 2026-05-27, but the only listed release is 0.1.10 from 2023-01-11 while the workspace declares 0.1.19. Check which version your dependency actually resolves.

Editorial conclusion

Adopt argh if you write small to medium Rust commandline tools and want help output derived from your existing doc comments, since the derive means the documentation and the parser cannot drift apart. Do not adopt it if you need runtime-defined argument sets or a parsing style the derive does not model, and do not expect a large crate behind it. Verify first that your target edition and Rust version suit a workspace pinned to edition 2018, and note the version gap: the workspace declares 0.1.19 while the only tagged release listed is 0.1.10 from 2023-01-11, so pin deliberately.

Frequently asked questions

How do I parse commandline arguments with argh?

Derive `FromArgs` on a struct, mark fields with `#[argh(switch)]`, `#[argh(option)]` or `#[argh(positional)]`, and call `argh::from_env()` in main. The doc comments on the struct and its fields become the help text.

How do I make an argh option optional or repeatable?

Wrap the field type in `Option<T>` to make it optional, use `Vec<T>` to make it repeating, and use `#[argh(default = "<expr>")]` to supply a default, which also makes the option optional.

Does argh support subcommands?

Yes. Declare a separate FromArgs struct per command, an enum marked with `#[argh(subcommand)]` casing over them, and a top-level struct with a `#[argh(subcommand)]` field. Use `name` and `short` on each command struct to set its invocation names.

What is the latest version of argh?

The workspace Cargo.toml declares version 0.1.19 with edition 2018 and a BSD-3-Clause licence, while the only listed GitHub release tag is 0.1.10 from 2023-01-11. The last push was on 2026-05-27, so check which version you actually resolve from crates.io.

Official sources

  1. google/argh on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
  5. Releases

Was this useful?