Rust

Building a CLI in Rust with clap

A practical walkthrough of building a real command-line tool in Rust using clap 4's derive API.

CLIs are where Rust really shines: fast startup, single binary, no runtime. Here’s how to build one properly.

Setup

[dependencies]
clap = { version = "4", features = ["derive"] }
anyhow = "1"

The derive API

clap 4’s derive API is the way to go. You define your CLI as structs, clap handles parsing:

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "mytool", version, about = "Does useful things")]
struct Cli {
    /// Enable verbose output
    #[arg(short, long)]
    verbose: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new project
    Init {
        #[arg(help = "Project name")]
        name: String,
    },
    /// Run the project
    Run {
        #[arg(short, long, default_value = "8080")]
        port: u16,
    },
}

Main entry point

fn main() -> anyhow::Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Init { name } => init(&name, cli.verbose),
        Commands::Run { port } => run(port, cli.verbose),
    }
}

One tip: separate your logic from CLI parsing

The main function should only parse args and dispatch. Keep your actual logic in functions that take plain types — not clap types. This makes your logic testable without invoking the CLI layer.

fn init(name: &str, verbose: bool) -> anyhow::Result<()> {
    // real logic here, no clap dependency
}

You get a clean binary, auto-generated --help, shell completions via clap_complete, and a structure that stays maintainable as the CLI grows.

Comments