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