Skip to content

Rust quickstart ​

Build a greet CLI with a subcommand, environment fallback, shell completions, and an integration test. You will need Rust and Cargo; install the Usage CLI when you reach documentation generation.

A new project ​

bash
cargo new greet
cd greet
mkdir -p src/cli tests

The example keeps the command-line declaration separate from the two command handlers:

text
greet/
├── Cargo.toml
├── src/
│   ├── main.rs
│   └── cli/
│       ├── mod.rs
│       ├── hello.rs
│       └── completion.rs
└── tests/
    └── cli.rs

Cargo.toml ​

toml
[dependencies]
usage = { package = "usage-rs", version = "6", features = ["completions"] }

[dev-dependencies]
usage = { package = "usage-rs", version = "6", features = ["test"] }

src/cli/mod.rs ​

rust
mod completion;
mod hello;

use completion::Completion;
use hello::Hello;
use usage::{Cli, Subcommands};

/// Greets people, politely
#[derive(Cli)]
#[usage(bin = "greet", version = "0.1.0", completion)]
pub(crate) struct Greet {
    #[usage(subcommand)]
    pub(crate) command: Commands,
}

#[derive(Subcommands)]
#[usage(run)] // Generate the match from each variant to its `Run` implementation.
pub(crate) enum Commands {
    Hello(Hello),
    Completion(Completion),
}

Doc comments on these types become help text. completion adds the shell-script methods and the hidden request that parse() handles when a completion script calls the binary.

src/cli/hello.rs ​

rust
use usage::{Args, Run};

/// Greet someone
#[derive(Args)]
pub(crate) struct Hello {
    /// Who to greet
    #[usage(env = "GREET_NAME", default = "world")]
    name: String,
}

impl Run for Hello {
    type Output = ();
    fn run(self) {
        println!("hello, {}", self.name);
    }
}

src/cli/completion.rs ​

rust
use usage::{Args, Run};

use super::Greet;

/// Print a completion script
#[derive(Args)]
pub(crate) struct Completion {
    /// Which shell to generate for
    #[usage(long, choices("bash", "zsh", "fish"))]
    shell: String,
}

impl Run for Completion {
    type Output = ();
    fn run(self) {
        let shell = match self.shell.as_str() {
            "bash" => usage::complete::Shell::Bash,
            "zsh" => usage::complete::Shell::Zsh,
            _ => usage::complete::Shell::Fish,
        };
        // Generate the script for the shell the user selected.
        print!("{}", Greet::completion_script(shell));
    }
}

src/main.rs ​

rust
mod cli;

use cli::Greet;
use usage::Run;

fn main() {
    // `parse()` handles help, version, errors, and completion requests.
    // `run()` is the match generated by `#[usage(run)]`.
    Greet::parse().command.run()
}

Run it ​

Build the executable and put this project's debug output on PATH for this shell. The examples below use a POSIX shell:

sh
cargo build
export PATH="$PWD/target/debug:$PATH"

You can also use cargo run -- hello instead of greet hello.

console
$ greet hello
hello, world

$ GREET_NAME=Jeff greet hello       # env fallback, declared on the field
hello, Jeff

Help comes from the types and their doc comments:

console
$ greet --help
greet 0.1.0
Greets people, politely

Usage: greet <SUBCOMMAND>

Commands:
  completion  Print a completion script
  hello       Greet someone
  help        Print this message or the help of the given subcommand(s)

Flags:
  -h, --help     Print help
  -V, --version  Print version

Errors are clap-shaped, exit status included:

console
$ greet helo
error: unrecognized subcommand 'helo'

  tip: a similar subcommand exists: 'hello'

Usage: greet <SUBCOMMAND>

For more information, try '--help'.

$ echo $?
2

Completions ​

usage-rs provides the same dynamic completion system that powers mise. The generated script is specific to the user's shell. When they press Tab, it calls back into greet; parse() handles that hidden request and returns candidates and descriptions for the command line being typed.

console
$ greet completion --shell fish | head -1
# @generated by usage-argv for `greet __complete_word__ --shell fish`

Tab-completing greet offers hello and completion with their descriptions; --shell offers bash, zsh, and fish. install_completion writes the generated script where that shell looks for it. See Completions for custom completers, aliases, file completion, and installation.

Docs and manpages ​

The generated spec can be passed to usage-cli for documentation, manpages, completion scripts, and other formats:

bash
greet __usage_spec__ > greet.usage.kdl
usage generate markdown --file greet.usage.kdl --multi --out-dir docs
usage g manpage  -f greet.usage.kdl > greet.1        # man page

This is the spec for the example CLI:

kdl
name greet
bin greet
version "0.1.0"
about "Greets people, politely"
subcommand_required #true
flag "-h --help" help="Print help" action=help builtin=#true
flag "-V --version" help="Print version" action=version builtin=#true
cmd hello help="Greet someone" {
    flag "-h --help" help="Print help" action=help builtin=#true
    arg "[NAME]" help="Who to greet" env=GREET_NAME default=world
}
cmd completion help="Print a completion script" {
    flag --shell help="Which shell to generate for" required=#true {
        arg <SHELL> {
            choices bash zsh fish
        }
    }
    flag "-h --help" help="Print help" action=help builtin=#true
}

tests/cli.rs ​

An integration test runs the compiled binary and captures the output a user sees:

rust
#[test]
fn hello_greets_by_name() {
    let output = usage::test::command!("greet", "hello", "Jeff").assert_success();

    assert_eq!(output.stdout_text(), "hello, Jeff\n");
}

command! also retains stderr and the exit status. See Testing for process-free parser assertions, help-page snapshots, and completion candidates.

Run the test from the project root:

sh
cargo test

Where next ​

MIT LicenseCopyright © 2026jdx.dev