Skip to content
Dev Tools Beginner Tutorial

Write Your First CLI Tool in Rust with Clap and Publish It to crates.io

Build a `greet` CLI with subcommands using Clap's derive API, then ship it to crates.io in minutes.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Jun 15, 2026 · 7 min read

What You'll Build

A greet CLI with two subcommands (hello and goodbye), short and long flags, and auto-generated help text — then published to crates.io for the world to install.

Prerequisites

  • Rust 1.74+ installed via rustup — run rustup update stable to ensure you're current
  • A free crates.io account (sign in with GitHub at crates.io) — verify your email address in account settings before publishing, or cargo publish will fail
  • macOS, Linux, or Windows with a terminal — no prior Rust experience required

1. Create the Project

cargo new greet-cli
cd greet-cli

This generates src/main.rs and Cargo.toml.

2. Add Clap as a Dependency

Open Cargo.toml and replace the [dependencies] section:

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

The derive feature unlocks #[derive(Parser)], which generates argument parsing from your struct automatically.

3. Write the CLI

Replace the entire contents of src/main.rs:

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "greet", version, about = "A friendly greeting tool")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Say hello to someone
    Hello {
        /// Name to greet
        #[arg(short, long)]
        name: String,
        /// Shout the greeting in uppercase
        #[arg(short, long)]
        shout: bool,
    },
    /// Say goodbye to someone
    Goodbye {
        /// Name to bid farewell
        #[arg(short, long)]
        name: String,
    },
}

fn main() {
    let cli = Cli::parse();
    match cli.command {
        Commands::Hello { name, shout } => {
            let msg = format!("Hello, {}!", name);
            if shout {
                println!("{}", msg.to_uppercase());
            } else {
                println!("{}", msg);
            }
        }
        Commands::Goodbye { name } => {
            println!("Goodbye, {}!", name);
        }
    }
}

How it works:

  • #[derive(Parser)] on Cli generates all argument-parsing logic from the struct shape.
  • #[command(subcommand)] wires Commands as the subcommand dispatcher.
  • #[arg(short, long)] auto-creates both -n/--name and -s/--shout flags from field names.
  • /// doc comments become the help text shown by --help.
  • version in #[command(...)] reads the version string directly from Cargo.toml.

4. Test Locally

cargo run -- hello --name Alice
cargo run -- hello --name Alice --shout
cargo run -- goodbye -n Bob
cargo run -- --help
cargo run -- hello --help

The -- separates cargo run arguments from your binary's arguments.

5. Prepare Cargo.toml for Publishing

crates.io requires description and license (or license-file). Edit Cargo.toml so it matches exactly:

[package]
name = "greet-cli"
version = "0.1.0"
edition = "2021"
description = "A friendly greeting CLI with hello/goodbye subcommands"
license = "MIT"
repository = "https://github.com/yourusername/greet-cli"

[[bin]]
name = "greet"
path = "src/main.rs"

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

The [[bin]] section explicitly sets the installed binary name to greet regardless of the package name. Without it, Cargo names the binary after the package (greet-cli), so greet hello would fail with "command not found" after install.

Pick a unique crate name. greet-cli may already be taken. Run cargo search your-chosen-name to check. The name field is exactly what users type in cargo install.

6. Publish to crates.io

  1. Go to https://crates.io/settings/tokens and create a new API token with the "Publish new crates" scope.
  2. Authenticate Cargo with that token:
cargo login

Cargo will prompt you to paste the token.

  1. Commit all your changes. cargo new initializes a Git repository, and cargo publish refuses to package a dirty working tree:
git add .
git commit -m "ready to publish"
  1. Dry-run first to catch packaging errors before anything goes live:
cargo publish --dry-run
  1. Ship it:
cargo publish

Email verification required. If you signed in to crates.io with GitHub but haven't verified your email, cargo publish will fail with an email verification error. Confirm your address at crates.io/settings/profile before this step.

Your crate is now live. Anyone can install it with:

cargo install greet-cli

Verify It Works

After cargo install, the binary lands in ~/.cargo/bin (already on your PATH if you used rustup). Run:

greet hello --name Alice --shout
# Expected: HELLO, ALICE!

greet --version
# Expected: greet 0.1.0

Troubleshooting

Error Cause Fix
error[E0432]: unresolved import clap::Parser Missing features = ["derive"] Add features = ["derive"] to the clap entry in Cargo.toml
the name … is already taken on publish Crate name collision on crates.io Choose a unique name in [package]
missing field 'description' on publish Required metadata absent Add description and license to [package]
X dirty files found in the working directory Uncommitted changes in the Git repo Run git add . && git commit -m "ready to publish"
greet: command not found after install ~/.cargo/bin not on PATH Run source ~/.cargo/env or restart your shell

Next Steps

  • Richer argument types: use Vec<String> for variadic inputs or Option<String> for optional flags — see the Clap derive reference.
  • Structured output: add serde + serde_json to support a --output json flag for scripting.
  • Releasing updates: bump version in Cargo.toml and run cargo publish again — all prior versions remain permanently immutable on crates.io.
  • Automate releases: use the publish-crates GitHub Action to publish on every tagged commit.
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading