Rust CLI + TUI
Portable command-line tools with explicit memory and resource control.
Clap · Ratatui when needed · Cargo
Complete guide (Markdown) · Structured record (JSON)
Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. Check scope. 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.
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.
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.
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.
Configuration: Serde-backed settings + config
Typed settings plus structured events/spans.
Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.
Application logging: tracing + configured subscriber
Typed settings plus structured events/spans.
Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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?