Build Rust CLI Project
Purpose
Implement Rust command-line behavior with a clear boundary between user input, command execution, domain logic, and output.
The practical goal is a CLI that is easy to test, has descriptive operator-facing messages, and validates with the repository's Cargo commands.
Source Check
Use repo-local Rust files, checked-out dependency sources, Dash MCP or Dash HTTP for installed Rust and Cargo docsets, and then official Rust and Cargo documentation when Dash/local coverage is missing or stale:
- The Rust Programming Language
- The Cargo Book
- Cargo package layout
- Cargo tests guide
std::process::ExitCodestd::env::args
For third-party argument parsers, error crates, config crates, terminal styling, or snapshot tools, prefer the repository's existing dependencies first. Add a new crate only when it removes real implementation complexity and comes from a fetchable registry or repository.
Workflow
- Inspect the CLI shape:
Cargo.tomlsrc/main.rssrc/bin/src/lib.rs- existing argument parser or command modules
- existing tests in
tests/or near command code
- Identify the command surface:
- command name
- subcommands
- flags and options
- positional arguments
- stdin, stdout, stderr, files, or environment variables
- exit-code expectations
- Keep behavior testable:
- parse user input into explicit command data
- put domain work in functions that accept normal Rust values
- keep printing and process exit decisions at the edge
- return structured errors or clear result types from internal functions
- Implement output deliberately:
- stdout for requested command output
- stderr for diagnostics, warnings, and failures
- descriptive errors that name the failed operation and likely cause
- stable text only where tests or downstream users depend on it
- Validate with targeted Cargo commands.
Dependency Decisions
Use the standard library for small internal tools when argument parsing is simple.
Use the repo's existing parser or error-handling crate when one is already established. If adding a new dependency, explain:
- what repeated parsing, validation, help, or error behavior it replaces
- why the standard library or existing dependency is not enough
- how the dependency affects MSRV, binary size, and package policy
Do not add a CLI framework only because one is popular.
Testing Strategy
Prefer fast tests around parsing and command execution functions:
#[test]
fn parses_verbose_flag() {
let command = parse_args(["tool", "--verbose", "input.txt"]).unwrap();
assert!(command.verbose);
}
Use process-level integration tests only for behavior that truly depends on the executable boundary, such as stdout, stderr, current directory, environment, or exit status.
Keep error-message tests focused on stable user-facing text.
Validation
Choose the narrowest command that proves the change:
cargo test -p package-name
cargo clippy -p package-name --all-targets --all-features
cargo fmt --check
Use workspace-wide commands when the CLI change crosses package boundaries.
Output Shape
Return:
Command surface: subcommands, flags, inputs, outputs, and exit behavior changed.Implementation boundary: what lives in parsing, execution, domain logic, and output code.Dependencies: reused, added, or intentionally avoided dependencies.Tests: unit, integration, or process-level coverage added or recommended.Validation: exact Cargo commands run or skipped with the concrete reason.Next skill: usuallyrust:testing-workflow,rust:tooling-style-workflow, orrust:package-workflow.
Guardrails
- Do not put domain behavior directly inside argument parsing code.
- Do not call
std::process::exitdeep inside reusable functions. - Do not write vague errors like
failedwithout naming the operation and likely cause. - Do not add new CLI dependencies without naming the implementation problem they solve.
- Do not make stdout/stderr text unstable if tests, scripts, or users depend on it.