# Rust CLI + TUI

Portable command-line tools with explicit memory and resource control.

Clap · Ratatui when needed · Cargo

[Complete guide (Markdown)](/stacks/rust-cli/guide.md) · [Structured record (JSON)](/stacks/rust-cli/record.json)

Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. [Check scope](/research-freshness.json). Dependency combinations and setup need project-specific qualification.

## Use cases

- Fast native developer tools

- parsers and analyzers

- shipped terminal applications

## Choose it when

- Need native performance or reuse of Rust libraries

- Want ownership/type checks around complex transformations

## Consider another path when

- Disposable automation would be simpler in Python or shell

- Build latency and cross-toolchains outweigh runtime needs

## Working practices

- Keep parsing separate from domain behavior. Test malformed input, pipes, errors and exit codes.

- Use interactive UI only when a terminal is available and the user has requested it.

- Bound resource use and retain error context rather than panicking on expected failures.

- Build with the committed lockfile and verify each supported release target.

## Tools by job

### Command parsing: clap

Typed commands plus a mature terminal widget/rendering ecosystem.

Keep domain code separate from rendering and support non-TTY output; restore terminal on errors and interruption.

- [https://docs.rs/clap/latest/clap/](https://docs.rs/clap/latest/clap/)

- [https://ratatui.rs/](https://ratatui.rs/)

### Terminal UI: Ratatui only for interactive screens

Typed commands plus a mature terminal widget/rendering ecosystem.

Keep domain code separate from rendering and support non-TTY output; restore terminal on errors and interruption.

- [https://docs.rs/clap/latest/clap/](https://docs.rs/clap/latest/clap/)

- [https://ratatui.rs/](https://ratatui.rs/)

### HTTP client: reqwest

Reusable typed HTTP client APIs.

Choose blocking or async deliberately; configure timeouts, TLS and response bounds. Never call blocking client inside an async runtime indiscriminately.

- [https://docs.rs/reqwest/latest/reqwest/](https://docs.rs/reqwest/latest/reqwest/)

### Persistence and migrations: None for stateless tools

Start without a database; add schema ownership only for persistent products.

SQLite feature/driver and migration setup require target testing; durable local state is not shared distributed workflow state.

- [https://docs.rs/sqlx/latest/sqlx/](https://docs.rs/sqlx/latest/sqlx/)

- [https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)

### Configuration: Serde-backed settings + config

Typed settings plus structured events/spans.

Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.

- [https://docs.rs/config/latest/config/](https://docs.rs/config/latest/config/)

- [https://doc.rust-lang.org/std/env/fn.var.html](https://doc.rust-lang.org/std/env/fn.var.html)

- [https://docs.rs/tracing/latest/tracing/](https://docs.rs/tracing/latest/tracing/)

### Application logging: tracing + configured subscriber

Typed settings plus structured events/spans.

Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.

- [https://docs.rs/config/latest/config/](https://docs.rs/config/latest/config/)

- [https://doc.rust-lang.org/std/env/fn.var.html](https://doc.rust-lang.org/std/env/fn.var.html)

- [https://docs.rs/tracing/latest/tracing/](https://docs.rs/tracing/latest/tracing/)

### Tests and static checks: cargo test; rustfmt; Clippy; cargo audit

Compiler, style, behavior and known-advisory checks complement each other.

Audit coverage is advisory-driven; compile success does not establish behavior or security. Test supported feature combinations rather than blindly enabling incompatible features.

- [https://doc.rust-lang.org/cargo/commands/cargo-test.html](https://doc.rust-lang.org/cargo/commands/cargo-test.html)

- [https://github.com/rust-lang/rustfmt](https://github.com/rust-lang/rustfmt)

- [https://doc.rust-lang.org/clippy/usage.html](https://doc.rust-lang.org/clippy/usage.html)

- [https://rustsec.org/](https://rustsec.org/)

- [https://docs.rs/assert_cmd/latest/assert_cmd/](https://docs.rs/assert_cmd/latest/assert_cmd/)

### Executable behavior tests: assert_cmd

Tests real exit codes, stdout/stderr and command contracts.

Snapshot changes need review; also test actual error and side-effect behavior.

- [https://docs.rs/assert_cmd/latest/assert_cmd/](https://docs.rs/assert_cmd/latest/assert_cmd/)

- [https://docs.rs/clap/latest/clap/](https://docs.rs/clap/latest/clap/)

### Durable work: Delegate to a durable service; retain resumable job identity

CLI lifetime does not own remote work lifetime.

Checkpoint schema, atomic persistence and external idempotency must be designed; async execution alone adds no durability.

- [https://docs.rs/restate-sdk/latest/restate_sdk/](https://docs.rs/restate-sdk/latest/restate_sdk/)

### Distribution: dist (formerly cargo-dist) + Cargo release builds

Generates multi-platform release pipelines and installers.

Review generated CI before tags can publish; test installers and target-native dependencies. Signing and notarization are separate platform work.

- [https://github.com/axodotdev/cargo-dist](https://github.com/axodotdev/cargo-dist)

- [https://doc.rust-lang.org/cargo/commands/cargo-build.html](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

### Machine-readable output: serde_json

Stable JSON output lets scripts consume commands without parsing terminal text

Add an explicit --json/output mode and document schema/exit codes; reserve stdout for results, stderr for diagnostics, and disable interactive/color output for pipelines.

- [https://docs.rs/serde_json/latest/serde_json/](https://docs.rs/serde_json/latest/serde_json/)

### Cancellation and shutdown: Tokio signal handling for async commands

User interrupts should cancel outbound work and leave terminal/files usable

Propagate cancellation through work; bound cleanup time and test interrupts during writes. Avoid adding an async runtime to a synchronous Rust CLI solely for signals.

- [https://docs.rs/tokio/latest/tokio/signal/index.html](https://docs.rs/tokio/latest/tokio/signal/index.html)

Build with the stack’s strengths

## Architecture and developer workflow.

Recommendation: clap derives/parses a stable command interface, domain operations return Result, and the entry point converts errors to streams/status after cleanup. Serde owns a versioned machine format; Ratatui is an optional terminal client. Cargo/dist prepare artifacts for explicitly supported targets and update channels.

Version scope: Reviewed clap 4.6.7 and Rust std 1.99.0 documentation; Serde, Ratatui and current axodotdev cargo-dist book. Pin rust-toolchain.toml/Cargo.lock and dist version/config. The older opensource.axo.dev book path failed; no target install or release workflow was executed. · Reviewed 2026-10-11

### Architecture boundaries

- Recommendation: use a library crate or modules for application operations and a thin binary adapter. Keep clap parsing errors separate from business errors; prefer explicit main ExitCode mapping when the contract requires more than generic failure.

- Recommendation: Serde input/output types carry schema versions. Separate strict user configuration from forward-compatible result reading; deny_unknown_fields is an intentional policy, not universal compatibility.

- Recommendation: encapsulate raw-mode/alternate-screen lifetime and restore the terminal on errors and panics. Share domain operations between Ratatui and a noninteractive JSON mode; interactive render state is not durable application state.

- Recommendation: bounded Rayon/Tokio work is selected by CPU versus I/O needs, with explicit cancellation and effect receipts. A compiled binary reduces interpreter deployment needs but does not remove database, TLS or platform dependencies.

### Tools and feedback

- Recommendation: start from existing Cargo manifest, consumers and rust-toolchain.toml. For a new local package, cargo new tool and cargo add clap --features derive are optional initial steps; add serde --features derive and serde_json only when a machine/config format is required.

- Recommendation: cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test --locked verify source checks in the selected feature matrix. Exercise subprocess stdout/stderr/status and panic/interrupt terminal restoration on supported OSs.

- Recommendation: cargo build --release --locked builds one selected target. Inspect its native/TLS dependencies and execute on clean target hosts; --target compilation alone does not qualify distribution.

- Recommendation: with a pinned dist tool, run dist init and review generated configuration/workflows before committing; init can overwrite manual workflow edits. dist plan --output-format=json previews artifact/announcement intent; actual target builds, installers and upgrade/downgrade checks remain required.

### Translations to avoid

- Avoid treating debug-formatted Result errors as a stable machine API; define error codes and schema deliberately.

- Avoid process::exit in cleanup-sensitive paths; terminal restoration and destructors need normal control flow.

- Avoid enabling raw terminal mode for piped/noninteractive invocations; they need predictable streams and no prompts.

- Avoid hand-editing generated dist workflows without recording how customization survives regeneration.

[docs.rs →](https://docs.rs/clap/latest/clap/)
[docs.rs →](https://docs.rs/clap/latest/clap/error/struct.Error.html)
[doc.rust-lang.org →](https://doc.rust-lang.org/std/process/struct.ExitCode.html)
[serde.rs →](https://serde.rs/container-attrs.html)
[ratatui.rs →](https://ratatui.rs/recipes/apps/panic-hooks/)
[tokio.rs →](https://tokio.rs/tokio/topics/shutdown)
[docs.rs →](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
[axodotdev.github.io →](https://axodotdev.github.io/cargo-dist/book/workspaces/simple-guide.html)
[axodotdev.github.io →](https://axodotdev.github.io/cargo-dist/book/reference/config.html)
[doc.rust-lang.org →](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

For an existing application

## Keep the contracts while you change the stack.

These are documented options and proposed checks. Inspect the actual source application before adopting a transition.

CLI and pipe contracts

### Inspect the source

Record current commands, option defaults, exit codes, stdout/stderr, JSON field names and shell completions consumed by callers.

### During coexistence

Recommendation: keep an old command adapter and explicit schema version while migrating parsing to clap. Preserve old names/aliases and map new domain errors into the established protocol.

### Target design

Recommendation: clap plus typed result/error JSON and main ExitCode mapping. clap usage failures use status 2 while help/version uses success; application failures need their own documented policy.

### Rollback

Recommendation: return consumers to the previous binary/schema while retaining old option adapters. Keep version selection explicit so a rollback does not silently alter JSON meaning.

### Benefits and prerequisites

**Typed parsing and a stable output envelope make command automation explicit.**
Requires: Contract fixtures cover old callers; result schemas and error/status mapping are versioned; help/completion behavior is checked.
Tradeoffs: Parser defaults can change compatibility; numeric status meanings are not universal across platforms; Rust error types do not define the public protocol.

### Checks before transfer

- Verify help/version succeed and invalid arguments use the intended stream/code without effects.

- Execute legacy scripts and compare stdout/stderr/status exactly where promised.

- Validate empty, partial and failed JSON results against the advertised schema.

[docs.rs →](https://docs.rs/clap/latest/clap/)
[docs.rs →](https://docs.rs/clap/latest/clap/error/struct.Error.html)
[doc.rust-lang.org →](https://doc.rust-lang.org/std/process/struct.ExitCode.html)

Data and schema

### Inspect the source

Inventory configuration file locations, schema versions, defaults, permissions and old binaries' ability to read newer files.

### During coexistence

Recommendation: read old config into a versioned internal model and migrate only through an explicit command with backup. Keep concurrent writer ownership explicit; read-only startup must not silently rewrite files.

### Target design

Recommendation: Serde-tagged/versioned configuration and documented unknown-field handling. Validate a temporary representation before replacing the live file; platform atomicity, permissions and crash behavior require implementation checks.

### Rollback

Recommendation: restore the preserved old config and binary as a pair. Reconcile new-only fields before using an old reader; avoid destructive in-place downgrade guesses.

### Benefits and prerequisites

**Explicit config migration decouples executable upgrades from accidental data loss.**
Requires: Old/new fixtures and semantic transforms exist; backup/restore works; unsupported versions fail before write.
Tradeoffs: Strict Serde unknown-field rejection can prevent mixed-version use; deny_unknown_fields does not compose with flatten; replacing a file is not a complete durability protocol.

### Checks before transfer

- Load old config and compare effective settings after migration.

- Verify unsupported versions and malformed values leave the source unchanged.

- Downgrade the binary with its preserved config and confirm paths/permissions on each target OS.

[serde.rs →](https://serde.rs/container-attrs.html)

Jobs and schedules

### Inspect the source

Preserve ordering, per-item effects, timeout and interrupt semantics for batch/network subcommands.

### During coexistence

Recommendation: retain sequential mode while qualifying bounded parallel work using dry-run/fixtures. Store item operation IDs and completion receipts before enabling resume against live systems.

### Target design

Recommendation: a bounded executor selected for CPU or async I/O with cooperative stop, tracked joins and cleanup. Started blocking work needs its own stop protocol or process boundary; do not label a task timeout as effect cancellation.

### Rollback

Recommendation: use sequential mode with the same receipt format and reconcile unknown outcomes. A previous executable must be able to read checkpoint versions or an explicit converter is required.

### Benefits and prerequisites

**Bounded parallel execution can overlap independent work without unlimited task creation.**
Requires: Independent item semantics; capacity/budget limits; stable effect IDs; tested cancellation and resume contract.
Tradeoffs: Speed is unmeasured; running blocking tasks may continue; a cancelled remote write can have an ambiguous outcome.

### Checks before transfer

- Interrupt a batch and verify no new items start, joins are bounded and terminal state restores.

- Simulate a remote success with a lost acknowledgement; resume reconciles rather than duplicating the effect.

- Verify CPU tasks cannot starve I/O and the executor respects the declared capacity.

[tokio.rs →](https://tokio.rs/tokio/topics/shutdown)
[docs.rs →](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
[ratatui.rs →](https://ratatui.rs/recipes/apps/panic-hooks/)

Delivery and recovery

### Inspect the source

Capture existing target triples, libc/native dependencies, install path, package metadata and update/downgrade contracts.

### During coexistence

Recommendation: prepare dist artifacts beside the previous release; review generated CI and installers, and use a prerelease or test channel for target qualification. Do not promise all platforms merely because dist generates a matrix.

### Target design

Recommendation: pinned dist configuration and Cargo.lock with release artifacts/installers for exercised targets. Keep signing/attestation, system dependency baselines and update channel ownership explicit.

### Rollback

Recommendation: restore the previous verified package/update channel and retain older installers/checksums. Review data/config compatibility before allowing downgrade; never change a public channel based only on a plan.

### Benefits and prerequisites

**Generated release plans coordinate binary artifacts and installers across targets.**
Requires: dist version is pinned; generated workflow is reviewed; native dependencies and every advertised target are exercised.
Tradeoffs: dist init may replace generated workflows; OS signing and Linux libc compatibility remain separate obligations; a plan is not build/publication evidence.

### Checks before transfer

- Inspect dist plan --output-format=json for expected target triples and artifact names.

- Install downloaded artifacts on clean target hosts, including the oldest promised Linux baseline.

- Exercise upgrade/downgrade, PATH behavior, checksum verification and preserved versioned config.

[axodotdev.github.io →](https://axodotdev.github.io/cargo-dist/book/workspaces/simple-guide.html)
[axodotdev.github.io →](https://axodotdev.github.io/cargo-dist/book/reference/config.html)
[doc.rust-lang.org →](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

[Read the migration workflow →](/rewrite/guide.md)

## Setup

- Create source in a local development workspace with cargo new tool; enter tool; run cargo add clap --features derive.

- Resolve Ratatui and its supported terminal backend only if interactive rendering is required; add reqwest only if network access is needed.

- Implement typed commands, injected/configured settings, stable output/exit codes and domain logic independent of terminal rendering.

- Add tests with cargo add --dev assert_cmd; cover missing files, invalid input, stdin, cancellation, stdout/stderr and terminal cleanup.

- Install a selected dist release and run dist init; review target list and generated CI. Lock dependencies/toolchain; no release tags or publication were executed.

## Verification

Run these in the configured application; they are instructions, not recorded passes.

```
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo audit
cargo build --release --locked
```

## Open questions

- Which terminals, non-TTY consumers and OS/architectures must be supported?

- What output schema, cancellation and configuration precedence will consumers rely on?

- Which native dependencies, signing/notarization and update channels are required?

## Sources

- [clap](https://docs.rs/clap/latest/clap/)

- [Ratatui](https://ratatui.rs/)

- [reqwest](https://docs.rs/reqwest/latest/reqwest/)

- [SQLx](https://docs.rs/sqlx/latest/sqlx/)

- [SQLx CLI](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)

- [Rust config crate](https://docs.rs/config/latest/config/)

- [Rust environment variables](https://doc.rust-lang.org/std/env/fn.var.html)

- [Rust tracing](https://docs.rs/tracing/latest/tracing/)

- [Cargo test](https://doc.rust-lang.org/cargo/commands/cargo-test.html)

- [rustfmt](https://github.com/rust-lang/rustfmt)

- [Clippy usage](https://doc.rust-lang.org/clippy/usage.html)

- [RustSec](https://rustsec.org/)

- [assert_cmd](https://docs.rs/assert_cmd/latest/assert_cmd/)

- [Restate Rust SDK](https://docs.rs/restate-sdk/latest/restate_sdk/)

- [dist binary distribution](https://github.com/axodotdev/cargo-dist)

- [Cargo build](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

- [Rust JSON API](https://docs.rs/serde_json/latest/serde_json/)

- [Rust signal API](https://docs.rs/tokio/latest/tokio/signal/index.html)

---

Canonical page: https://stack.menu/stacks/rust-cli/
