Flag-rs - A Cobra-inspired CLI Framework for Rust
Flag-rs is a command-line interface (CLI) framework for Rust inspired by Go's Cobra library. It provides dynamic command completion, self-registering commands, and a clean, modular architecture for building sophisticated CLI applications.
Key Features
- Zero Dependencies - Pure Rust implementation with no external crates
- Multi-Level Nested Commands - Full support for commands like
myapp deploy statuswith unlimited nesting - Dynamic Runtime Completions - Generate completions based on runtime state (like kubectl)
- Working Zsh Subcommand Completion - Tab completion that actually works for nested commands in zsh, bash, and fish
- Self-Registering Commands - Commands can register themselves with parent commands
- Modular Architecture - Organize commands in separate files/modules
- Hierarchical Flag Inheritance - Global flags are available to all subcommands
- Idiomatic Error Handling - Uses standard Rust
Resulttypes - Colored Output - Beautiful help messages with ANSI color support (respects NO_COLOR and terminal detection)
Why Flag-rs?
Flag-rs solves specific pain points that other CLI frameworks struggle with:
- Dynamic completions that actually work - Query APIs, databases, or any runtime state at tab-press time, just like
kubectldoes when completing pod names - Reliable zsh subcommand completion - Multi-level commands like
myapp deploy statusget proper tab completion in various shells.
Why Not Flag-rs?
The Flag-rs implementation may be naive - the leading crate, Clap, is very well regarded and meets the needs of a huge knowledgeable user base. Choose clap if you need maximum stability,
extensive documentation, and don't require dynamic completions.
Installation
Add this to your Cargo.toml:
[]
= "0.8"
Quick Start
use ;
Nested Commands with Tab Completion
Flag-rs fully supports multi-level command hierarchies with working tab completion at every level. This is especially valuable for zsh users who have experienced issues with other frameworks.
Building Nested Commands
Create commands like myapp deploy status and myapp deploy publish:
use ;
Tab Completion Behavior
With the above setup:
myapp <TAB>→ showsdeploy(and any other root commands)myapp deploy <TAB>→ showsstatus,publishmyapp deploy status <TAB>→ showsweb-api,worker,database
This works correctly in bash, zsh, and fish shells.
Arbitrary Nesting Depth
The architecture supports unlimited nesting levels:
new // Level 1
.subcommand
.build
Results in: myapp cloud compute instances list with tab completion at each level.
Dynamic Completions
The killer feature of Flag-rs is dynamic completions. Unlike static completions, these run at completion time and can return different values based on current state:
new
.arg_completion
.build
Completions with Descriptions
Flag-rs supports rich completions with descriptions, similar to Cobra. When descriptions are provided, they are handled appropriately by each shell:
.flag_completion
Shell-specific behavior:
- Bash: Shows only the completion values (descriptions not supported natively)
- Zsh: Shows descriptions using native format:
value:description - Fish: Shows descriptions using tab-separated format:
value[TAB]description
For Zsh and Fish, descriptions appear alongside completions in the shell's native format, providing helpful context when selecting options.
Completion Caching
For expensive completion operations (API calls, file system scans), use the built-in caching mechanism:
use CompletionCache;
use Duration;
use Arc;
let cache = new;
new
.arg_completion
.build
Completion Timeouts
Protect against slow completion functions that could hang the shell:
use make_timeout_completion;
use Duration;
new
.arg_completion
.build
Modular Command Structure
For larger applications, Flag supports a modular structure where commands register themselves:
// src/cmd/mod.rs
// src/cmd/serve.rs
Shell Completions
Flag-rs generates working completion scripts for bash, zsh, and fish. Unlike some frameworks, nested subcommands work correctly in zsh.
Adding Completion Support
// Add a completion command to your CLI
new
.short
.run
.build
Enabling Completions
Users can enable completions in their shell:
# Bash
# Zsh - works correctly for nested subcommands!
# Fish
|
What Works
All shells support:
- ✅ Root command completion
- ✅ Nested subcommand completion (including zsh!)
- ✅ Flag completion with descriptions (zsh and fish show descriptions)
- ✅ Dynamic argument completion
- ✅ Flag value completion
The completion system navigates the full command hierarchy, so typing myapp deploy <TAB> correctly shows subcommands at that level, even in zsh.
Flag Types
Flag-rs supports multiple value types:
String- Text valuesBool- Boolean flagsInt- Integer valuesFloat- Floating point valuesStringSlice- Multiple string valuesStringArray- Array of string valuesChoice(Vec<String>)- Enumerated choices with validationRange(i64, i64)- Integer range validationFile- File path validationDirectory- Directory path validation
Advanced Flag Features
Flag Constraints
Flag-rs supports advanced flag relationships:
use ;
// Required if another flag is set
new
.value_type
.constraint
// Conflicts with other flags
new
.value_type
.constraint
// Requires other flags
new
.value_type
.constraint
Choice Validation
new
.value_type
Range Validation
new
.value_type
.default
Error Handling
Flag uses idiomatic Rust error handling with no forced dependencies:
Examples
See the examples/ directory for complete examples:
kubectl.rs- A kubectl-like CLI demonstrating three-level nested commands (kubectl get pods) with dynamic completions and working zsh tab completionkubectl_modular/- A modular kubectl implementation showing command self-registration across multiple filesadvanced_flags_demo.rs- Demonstrates advanced flag types and constraintscaching_demo.rs- Shows completion caching for expensive operationstimeout_demo.rs- Demonstrates timeout protection for slow completionsmemory_optimization_demo.rs- Shows memory optimization techniquesbenchmark.rs- Performance benchmarking suite
Quick test of nested commands with completion:
# Build the kubectl example
# Try the three-level command structure
# Generate and test zsh completion
Performance & Memory Optimization
Flag-rs includes several performance optimizations for large-scale CLI applications:
String Interning
Reduce memory usage for repeated strings:
use string_pool;
// Intern frequently used strings
let cmd_name = intern;
let flag_name = intern;
Optimized Completions
Use memory-efficient completion structures:
use ;
use Cow;
new
.add_with_description
Performance Characteristics
Based on our benchmarks:
| Operation | Performance | Notes |
|---|---|---|
| Simple command creation | ~500 ns | 2 flags |
| Complex CLI (50 subcommands) | ~150 μs | 500 total commands |
| Flag parsing | ~2 μs | Per flag |
| Subcommand lookup | ~50 ns | O(1) HashMap |
| Dynamic completion | ~15 μs | 100 items |
| Cached completion | ~1 μs | Cache hit |
See examples/benchmark.rs for detailed performance measurements.
Design Philosophy
Flag is designed to be a foundational library with zero dependencies. This ensures:
- Fast compilation times
- No dependency conflicts
- Maximum flexibility for users
- Minimal binary size
Users can add their own dependencies for features like colored output, async runtime, or configuration file support without conflicts.
Development
The justfile is the single source of truth for build/test/lint. CI calls
just ci and nothing else, so running it locally exactly matches CI:
The Rust toolchain is pinned in rust-toolchain.toml (currently 1.95.0). The
same version is pinned in the CI workflow — both must agree.
Continuous Integration
CI runs on Forgejo Actions (.forgejo/workflows/ci-linux.yml) on every PR to
main and on manual dispatch. It checks out the repo, installs just and the
pinned Rust toolchain, then runs just ci.
Publishing Releases
Releases run on Forgejo Actions (.forgejo/workflows/release.yml) and are
triggered by pushing a v* tag. The workflow rewrites the version in
Cargo.toml to match the tag (with the v stripped), commits the bump to
main, runs the test suite, then publishes to crates.io.
That's it. The tag's v is stripped before anything touches Cargo.toml or
crates.io, so the published version is plain semver (0.9.0) — continuous
with the existing publish history.
Required Forgejo repo secrets (Settings → Actions → Secrets):
PAT— Forgejo personal access token withwrite:repositoryscope. Needed so the workflow can push the version-bump commit back tomain.CRATES_IO_TOKEN— API token from https://crates.io/settings/tokens.
License
MIT
Developer env
source <(./target/debug/examples/kubectl completion zsh)